use super::*;
use crate::test_support::{
    TestAppendFile, TestDeleteFile, test_file_column_stats, test_file_partition_value,
    test_inlined_file_deletion,
};
use crate::{
    AddColumn, AppendFile, CreateTableColumn, PartitionColumnCommit, PartitioningBatchCommit,
    PartitioningTableCommit, SortExpressionCommit,
};
use ducklake_catalog::{DeleteFileId, DeleteFormat};
use std::{collections::HashMap, fs, sync::Arc};

use arrow::array::{
    Array, BinaryArray, BooleanArray, Date32Array, Decimal128Array, Decimal256Array,
    FixedSizeBinaryArray, Float32Array, Float64Array, Int8Array, Int16Array, Int32Array,
    Int64Array, IntervalMonthDayNanoArray, ListArray, MapArray, StringArray, StructArray,
    Time64MicrosecondArray, Time64NanosecondArray, TimestampMicrosecondArray,
    TimestampMillisecondArray, TimestampNanosecondArray, TimestampSecondArray, UInt8Array,
    UInt16Array, UInt32Array, UInt64Array,
};
use arrow::datatypes::{DataType, Field, IntervalMonthDayNano, Schema, i256};
use arrow::record_batch::RecordBatch;

static QUACK_LIVE_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());

#[test]
fn parses_quack_uri_like_duckdb_extension() {
    let parsed = QuackUri::parse_with_ssl(" quack://ilum-quack:9494 ", false).unwrap();
    assert_eq!(parsed.raw(), "quack://ilum-quack:9494");
    assert_eq!(parsed.host(), "ilum-quack");
    assert_eq!(parsed.port(), 9494);
    assert!(!parsed.is_ipv6());
    assert!(!parsed.ssl());
    assert_eq!(parsed.http_url(), "http://ilum-quack:9494");
    assert_eq!(parsed.canonical_uri(), "quack:ilum-quack:9494");
    assert_eq!(parsed.quack_endpoint(), "http://ilum-quack:9494/quack");
    assert!(!parsed.is_local());

    let defaulted = QuackUri::parse("quack:localhost").unwrap();
    assert_eq!(defaulted.host(), "localhost");
    assert_eq!(defaulted.port(), DEFAULT_PORT);
    assert!(defaulted.ssl());
    assert_eq!(defaulted.http_url(), "https://localhost:9494");
    assert_eq!(defaulted.canonical_uri(), "quack:localhost:9494");
    assert!(defaulted.is_local());

    let ipv6 = QuackUri::parse("quack:[::1]:1234").unwrap();
    assert_eq!(ipv6.host(), "::1");
    assert_eq!(ipv6.port(), 1234);
    assert!(ipv6.is_ipv6());
    assert_eq!(ipv6.http_url(), "https://[::1]:1234");
    assert_eq!(ipv6.canonical_uri(), "quack:::1:1234");
    assert!(ipv6.is_local());
}

#[test]
fn quack_catalog_uses_duckdb_client_ssl_defaults() {
    let local = QuackCatalog::try_new("quack:localhost", "secret-token").unwrap();
    assert!(!local.uri().ssl());
    assert_eq!(local.uri().http_url(), "http://localhost:9494");

    let remote = QuackCatalog::try_new("quack:example.com", "secret-token").unwrap();
    assert!(remote.uri().ssl());
    assert_eq!(remote.uri().http_url(), "https://example.com:9494");

    let explicit_ssl =
        QuackCatalog::try_new_with_ssl("quack:localhost", "secret-token", true).unwrap();
    assert!(explicit_ssl.uri().ssl());
    assert_eq!(explicit_ssl.uri().http_url(), "https://localhost:9494");
}

#[test]
fn qualifies_quack_metadata_sql_without_rewriting_literals_or_column_refs() {
    let sql = "\
SELECT ducklake_data_file.data_file_id \
FROM ducklake_data_file \
WHERE note = 'ducklake_table isn''t a table here' \
  AND EXISTS (SELECT 1 FROM ducklake_snapshot s WHERE s.snapshot_id = ducklake_data_file.begin_snapshot) \
  AND table_name = ducklake_quack_client_mutation_test.answer";
    let qualified = quack_qualify_metadata_sql(sql, "ducklake_meta");

    assert!(qualified.contains("FROM \"ducklake_meta\".ducklake_data_file"));
    assert!(qualified.contains("FROM \"ducklake_meta\".ducklake_snapshot s"));
    assert!(qualified.contains("SELECT ducklake_data_file.data_file_id"));
    assert!(qualified.contains("ducklake_data_file.begin_snapshot"));
    assert!(qualified.contains("'ducklake_table isn''t a table here'"));
    assert!(qualified.contains("ducklake_quack_client_mutation_test.answer"));

    let already_qualified = "SELECT * FROM \"ducklake_meta\".\"ducklake_inlined_data_1_1\"";
    assert_eq!(
        quack_qualify_metadata_sql(already_qualified, "ducklake_meta"),
        already_qualified
    );
}

#[test]
fn quack_catalog_tracks_optional_metadata_catalog() {
    let catalog = QuackCatalog::try_new("quack:localhost", "secret-token")
        .unwrap()
        .with_metadata_catalog("ducklake_meta")
        .unwrap();
    assert_eq!(catalog.metadata_catalog(), Some("ducklake_meta"));
    assert_eq!(
        catalog.metadata_table_identifier("ducklake_table"),
        "\"ducklake_meta\".\"ducklake_table\""
    );
}

#[test]
fn quack_sql_errors_do_not_invalidate_transaction_connection() {
    let sql_err = StorageError::Backend("Catalog Error: table already exists".into());
    assert!(!quack_error_invalidates_connection(&sql_err));

    let conflict = StorageError::conflict("append", SnapshotId(1), 2);
    assert!(!quack_error_invalidates_connection(&conflict));

    let transport_err = StorageError::Backend(
            "Quack query \"SELECT 1\" failed: backend error: failed to send Quack request: connection refused"
                .into(),
        );
    assert!(quack_error_invalidates_connection(&transport_err));
}

#[test]
fn quack_catalog_does_not_double_qualify_dynamic_metadata_table_refs() {
    let catalog = QuackCatalog::try_new("quack:localhost", "secret-token")
        .unwrap()
        .with_metadata_catalog("ducklake_meta")
        .unwrap();

    assert_eq!(
        catalog.dynamic_metadata_table_identifier("\"ducklake_meta\".ducklake_inlined_data_1_1"),
        "\"ducklake_meta\".\"ducklake_inlined_data_1_1\""
    );
    assert_eq!(
        catalog.dynamic_metadata_table_identifier("ducklake_meta.ducklake_inlined_data_1_1"),
        "\"ducklake_meta\".\"ducklake_inlined_data_1_1\""
    );
    assert_eq!(
        catalog.dynamic_metadata_table_identifier("\"other\".ducklake_inlined_data_1_1"),
        "\"ducklake_meta\".\"\"\"other\"\".ducklake_inlined_data_1_1\""
    );
}

#[test]
fn parses_quack_duckdb_timestamp_text_to_micros() {
    assert_eq!(
        quack_parse_timestamp_micros("1970-01-01 00:00:00+00"),
        Some(0)
    );
    assert_eq!(
        quack_parse_timestamp_micros("2026-06-28 02:16:45.466449+02"),
        Some(1_782_605_805_466_449)
    );
    assert_eq!(
        quack_parse_timestamp_micros("2026-06-28T00:16:45.466449Z"),
        Some(1_782_605_805_466_449)
    );
}

#[test]
fn reads_quack_sqlite_boolean_integers() {
    let values = [DuckDbValue::Int64(1), DuckDbValue::UInt8(0)];
    let row = QuackRow { values: &values };
    assert!(row.get_bool(0).unwrap());
    assert!(!row.get_bool(1).unwrap());

    let values = [DuckDbValue::Int32(2)];
    let row = QuackRow { values: &values };
    assert!(format!("{}", row.get_bool(0).unwrap_err()).contains("0 or 1"));
}

#[test]
fn reads_quack_uuid_metadata_columns_as_strings() {
    let values = [DuckDbValue::Uuid([
        0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44, 0x00,
        0x01,
    ])];
    let row = QuackRow { values: &values };
    assert_eq!(
        row.get_string(0).unwrap(),
        "550e8400-e29b-41d4-a716-446655440001"
    );
}

#[test]
fn rejects_quack_uri_failures_like_duckdb_extension() {
    assert!(format!("{}", QuackUri::parse("").unwrap_err()).contains("needs to start"));
    assert!(format!("{}", QuackUri::parse("quack:").unwrap_err()).contains("Missing hostname"));
    assert!(format!("{}", QuackUri::parse("quack::").unwrap_err()).contains("Invalid Port"));
    assert!(
        format!("{}", QuackUri::parse("quack:localhost:asdf").unwrap_err())
            .contains("Invalid Port")
    );
    assert!(format!("{}", QuackUri::parse("quack:[::1").unwrap_err()).contains("Invalid IPv6 URL"));
}

#[test]
fn models_quack_message_type_values_and_requests() {
    assert_eq!(MessageType::ConnectionRequest as u8, 1);
    assert_eq!(MessageType::FetchRequest as u8, 7);
    assert_eq!(MessageType::ErrorResponse as u8, 100);
    assert_eq!(
        MessageType::try_from(3).unwrap(),
        MessageType::PrepareRequest
    );
    assert!(MessageType::try_from(99).is_err());
    assert_eq!(MessageType::PrepareRequest.as_str(), "PREPARE_REQUEST");

    let catalog = QuackCatalog::try_new("quack:localhost", "secret-token").unwrap();
    let QuackRequest::Connection(request) = catalog.connection_request() else {
        panic!("expected connection request");
    };
    assert_eq!(request.header.message_type, MessageType::ConnectionRequest);
    assert_eq!(request.auth_string, "secret-token");
    assert_eq!(request.min_supported_quack_version, QUACK_VERSION);
    assert_eq!(request.max_supported_quack_version, QUACK_VERSION);
    assert_eq!(request.header.connection_id, "");
    assert_eq!(
        QuackRequest::Connection(request.clone()).expected_response_type(),
        MessageType::ConnectionResponse
    );

    let prepare = PrepareRequest::new("conn-1", "SELECT 1");
    assert_eq!(prepare.header.connection_id, "conn-1");
    assert_eq!(prepare.sql_query, "SELECT 1");
}

#[test]
fn models_duckdb_generated_request_property_contract() {
    assert_eq!(
        QuackRequest::duckdb_header_descriptors(),
        &[
            DuckDbPropertyDescriptor::new(1, "type", DuckDbPropertyKind::MessageType, false),
            DuckDbPropertyDescriptor::new(2, "connection_id", DuckDbPropertyKind::String, true,),
            DuckDbPropertyDescriptor::new(
                3,
                "client_query_id",
                DuckDbPropertyKind::OptionalIdx,
                false,
            ),
        ]
    );

    let catalog = QuackCatalog::try_new("quack:localhost", "secret-token").unwrap();
    let connection = catalog.connection_request().with_client_query_id(7);
    assert_eq!(
        connection.duckdb_body_descriptors(),
        &[
            DuckDbPropertyDescriptor::new(1, "auth_string", DuckDbPropertyKind::String, true,),
            DuckDbPropertyDescriptor::new(
                2,
                "client_duckdb_version",
                DuckDbPropertyKind::String,
                true,
            ),
            DuckDbPropertyDescriptor::new(3, "client_platform", DuckDbPropertyKind::String, true,),
            DuckDbPropertyDescriptor::new(
                4,
                "min_supported_quack_version",
                DuckDbPropertyKind::Idx,
                true,
            ),
            DuckDbPropertyDescriptor::new(
                5,
                "max_supported_quack_version",
                DuckDbPropertyKind::Idx,
                true,
            ),
        ]
    );
    let plan = connection.duckdb_message_plan();
    assert_eq!(
        plan.header
            .iter()
            .map(|property| property.descriptor.name)
            .collect::<Vec<_>>(),
        vec!["type", "connection_id", "client_query_id"]
    );
    assert_eq!(
        plan.header[0].value,
        DuckDbPropertyValue::MessageType(MessageType::ConnectionRequest)
    );
    assert_eq!(
        plan.header[2].value,
        DuckDbPropertyValue::OptionalIdx(Some(7))
    );
    assert_eq!(
        plan.body[0].value,
        DuckDbPropertyValue::String("secret-token".into())
    );
    assert_eq!(plan.body[3].value, DuckDbPropertyValue::Idx(QUACK_VERSION));
    assert_eq!(plan.body[4].value, DuckDbPropertyValue::Idx(QUACK_VERSION));

    let prepare = QuackRequest::Prepare(PrepareRequest::new("conn-1", "SELECT 1"));
    assert_eq!(
        prepare.duckdb_body_descriptors(),
        &[DuckDbPropertyDescriptor::new(
            1,
            "sql_query",
            DuckDbPropertyKind::String,
            true,
        )]
    );
    assert_eq!(
        prepare.duckdb_message_plan().body[0].value,
        DuckDbPropertyValue::String("SELECT 1".into())
    );

    let fetch = QuackRequest::Fetch(FetchRequest::new("conn-1", -1));
    assert_eq!(
        fetch.duckdb_body_descriptors(),
        &[DuckDbPropertyDescriptor::new(
            1,
            "uuid",
            DuckDbPropertyKind::HugeInt,
            false,
        )]
    );
    assert_eq!(
        fetch.duckdb_message_plan().body[0].value,
        DuckDbPropertyValue::HugeInt(-1)
    );

    let disconnect = QuackRequest::Disconnect(DisconnectRequest::new("conn-1"));
    assert!(disconnect.duckdb_body_descriptors().is_empty());
    assert!(disconnect.duckdb_message_plan().body.is_empty());
}

#[test]
fn serializes_quack_requests_with_duckdb_binary_object_framing() {
    let prepare =
        QuackRequest::Prepare(PrepareRequest::new("conn-1", "SELECT 1")).with_client_query_id(99);
    assert_eq!(
        prepare.to_duckdb_binary().unwrap(),
        vec![
            1, 0, 3, 2, 0, 6, b'c', b'o', b'n', b'n', b'-', b'1', 3, 0, 99, 0xff, 0xff, 1, 0, 8,
            b'S', b'E', b'L', b'E', b'C', b'T', b' ', b'1', 0xff, 0xff,
        ]
    );

    let connection = QuackRequest::Connection(ConnectionRequest {
        header: MessageHeader::new(MessageType::ConnectionRequest, ""),
        auth_string: "secret".into(),
        client_duckdb_version: String::new(),
        client_platform: String::new(),
        min_supported_quack_version: 0,
        max_supported_quack_version: 0,
    });
    assert_eq!(
        connection.to_duckdb_binary().unwrap(),
        vec![
            1, 0, 1, 3, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 1, 0xff, 0xff, 1,
            0, 6, b's', b'e', b'c', b'r', b'e', b't', 0xff, 0xff,
        ]
    );

    let fetch = QuackRequest::Fetch(FetchRequest::new("conn-1", -1)).with_client_query_id(0);
    assert_eq!(
        fetch.to_duckdb_binary().unwrap(),
        vec![
            1, 0, 7, 2, 0, 6, b'c', b'o', b'n', b'n', b'-', b'1', 3, 0, 0, 0xff, 0xff, 1, 0, 0x7f,
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 1, 0xff, 0xff,
        ]
    );

    let disconnect = QuackRequest::Disconnect(DisconnectRequest::new("conn-1"));
    assert_eq!(
        disconnect.to_duckdb_binary().unwrap(),
        vec![
            1, 0, 11, 2, 0, 6, b'c', b'o', b'n', b'n', b'-', b'1', 3, 0, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, 0xff, 1, 0xff, 0xff, 0xff, 0xff,
        ]
    );
}

fn duckdb_response_bytes(header: Vec<DuckDbProperty>, body: Vec<DuckDbProperty>) -> Vec<u8> {
    let mut writer = DuckDbBinaryWriter::default();
    writer.write_object(&header).unwrap();
    writer.write_object(&body).unwrap();
    writer.finish()
}

fn duckdb_response_header(
    message_type: MessageType,
    connection_id: &str,
    client_query_id: Option<u64>,
) -> Vec<DuckDbProperty> {
    vec![
        DuckDbProperty::new(
            HEADER_PROPERTIES[0],
            DuckDbPropertyValue::MessageType(message_type),
        ),
        DuckDbProperty::new(
            HEADER_PROPERTIES[1],
            DuckDbPropertyValue::String(connection_id.into()),
        ),
        DuckDbProperty::new(
            HEADER_PROPERTIES[2],
            DuckDbPropertyValue::OptionalIdx(client_query_id),
        ),
    ]
}

fn write_duckdb_logical_type(writer: &mut DuckDbBinaryWriter, type_id: u64) {
    writer.write_field_id(100);
    writer.write_unsigned_leb128(type_id);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_logical_type_with_extra_info(
    writer: &mut DuckDbBinaryWriter,
    type_id: u64,
    write_extra_info: impl FnOnce(&mut DuckDbBinaryWriter),
) {
    writer.write_field_id(100);
    writer.write_unsigned_leb128(type_id);
    writer.write_field_id(101);
    writer.output.push(1);
    write_extra_info(writer);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_i64_value(writer: &mut DuckDbBinaryWriter, type_id: u64, value: i64) {
    writer.write_field_id(100);
    write_duckdb_logical_type(writer, type_id);
    writer.write_field_id(101);
    writer.output.push(0);
    writer.write_field_id(102);
    writer.write_signed_leb128(value);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_f64_value(writer: &mut DuckDbBinaryWriter, value: f64) {
    writer.write_field_id(100);
    write_duckdb_logical_type(writer, 23);
    writer.write_field_id(101);
    writer.output.push(0);
    writer.write_field_id(102);
    writer.output.extend_from_slice(&value.to_le_bytes());
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_string_value(writer: &mut DuckDbBinaryWriter, value: &str) {
    writer.write_field_id(100);
    write_duckdb_logical_type(writer, 25);
    writer.write_field_id(101);
    writer.output.push(0);
    writer.write_field_id(102);
    writer.write_string(value);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_decimal_value(writer: &mut DuckDbBinaryWriter, width: u8, scale: u8, value: i128) {
    writer.write_field_id(100);
    write_duckdb_logical_type_with_extra_info(writer, 21, |writer| {
        write_duckdb_decimal_extra_info(writer, width, scale);
    });
    writer.write_field_id(101);
    writer.output.push(0);
    writer.write_field_id(102);
    if width <= 18 {
        writer.write_signed_leb128(value as i64);
    } else {
        writer.write_hugeint(value);
    }
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_type_value(
    writer: &mut DuckDbBinaryWriter,
    write_type_value: impl FnOnce(&mut DuckDbBinaryWriter),
) {
    writer.write_field_id(100);
    write_duckdb_logical_type(writer, 6);
    writer.write_field_id(101);
    writer.output.push(0);
    writer.write_field_id(102);
    write_type_value(writer);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_interval_value(
    writer: &mut DuckDbBinaryWriter,
    months: i32,
    days: i32,
    micros: i64,
) {
    writer.write_field_id(100);
    write_duckdb_logical_type(writer, 27);
    writer.write_field_id(101);
    writer.output.push(0);
    writer.write_field_id(102);
    if months != 0 {
        writer.write_field_id(1);
        writer.write_signed_leb128(i64::from(months));
    }
    if days != 0 {
        writer.write_field_id(2);
        writer.write_signed_leb128(i64::from(days));
    }
    if micros != 0 {
        writer.write_field_id(3);
        writer.write_signed_leb128(micros);
    }
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_type_expression_extra_info(
    writer: &mut DuckDbBinaryWriter,
    catalog: &str,
    schema: &str,
    type_name: &str,
    write_children: impl FnOnce(&mut DuckDbBinaryWriter),
    child_count: u64,
) {
    writer.write_field_id(204);
    writer.output.push(1);
    write_duckdb_parsed_expression_base(writer, 21, 207);
    if !catalog.is_empty() {
        writer.write_field_id(200);
        writer.write_string(catalog);
    }
    if !schema.is_empty() {
        writer.write_field_id(201);
        writer.write_string(schema);
    }
    if !type_name.is_empty() {
        writer.write_field_id(202);
        writer.write_string(type_name);
    }
    if child_count != 0 {
        writer.write_field_id(203);
        writer.write_unsigned_leb128(child_count);
        write_children(writer);
    }
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_type_expression_value(
    writer: &mut DuckDbBinaryWriter,
    alias: &str,
    catalog: &str,
    schema: &str,
    type_name: &str,
    write_children: impl FnOnce(&mut DuckDbBinaryWriter),
    child_count: u64,
) {
    writer.output.push(1);
    write_duckdb_parsed_expression_base_with_alias(writer, 21, 207, alias);
    if !catalog.is_empty() {
        writer.write_field_id(200);
        writer.write_string(catalog);
    }
    if !schema.is_empty() {
        writer.write_field_id(201);
        writer.write_string(schema);
    }
    if !type_name.is_empty() {
        writer.write_field_id(202);
        writer.write_string(type_name);
    }
    if child_count != 0 {
        writer.write_field_id(203);
        writer.write_unsigned_leb128(child_count);
        write_children(writer);
    }
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_constant_expression_value(
    writer: &mut DuckDbBinaryWriter,
    write_value: impl FnOnce(&mut DuckDbBinaryWriter),
) {
    write_duckdb_constant_expression_value_with_alias(writer, "", write_value);
}

fn write_duckdb_constant_expression_value_with_alias(
    writer: &mut DuckDbBinaryWriter,
    alias: &str,
    write_value: impl FnOnce(&mut DuckDbBinaryWriter),
) {
    writer.output.push(1);
    write_duckdb_parsed_expression_base_with_alias(writer, 7, 75, alias);
    writer.write_field_id(200);
    write_value(writer);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_column_ref_expression_value(writer: &mut DuckDbBinaryWriter, names: &[&str]) {
    writer.output.push(1);
    write_duckdb_parsed_expression_base(writer, 4, 203);
    writer.write_field_id(200);
    writer.write_unsigned_leb128(names.len() as u64);
    for name in names {
        writer.write_string(name);
    }
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_comparison_expression_value(
    writer: &mut DuckDbBinaryWriter,
    expression_type: u64,
    write_left: impl FnOnce(&mut DuckDbBinaryWriter),
    write_right: impl FnOnce(&mut DuckDbBinaryWriter),
) {
    writer.output.push(1);
    write_duckdb_parsed_expression_base(writer, 5, expression_type);
    writer.write_field_id(200);
    write_left(writer);
    writer.write_field_id(201);
    write_right(writer);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_operator_expression_value(
    writer: &mut DuckDbBinaryWriter,
    expression_type: u64,
    write_children: impl FnOnce(&mut DuckDbBinaryWriter),
    child_count: u64,
) {
    writer.output.push(1);
    write_duckdb_parsed_expression_base(writer, 10, expression_type);
    if child_count != 0 {
        writer.write_field_id(200);
        writer.write_unsigned_leb128(child_count);
        write_children(writer);
    }
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_cast_expression_value(
    writer: &mut DuckDbBinaryWriter,
    cast_type_id: u64,
    try_cast: bool,
    write_child: impl FnOnce(&mut DuckDbBinaryWriter),
) {
    writer.output.push(1);
    write_duckdb_parsed_expression_base(writer, 3, 225);
    writer.write_field_id(200);
    write_child(writer);
    writer.write_field_id(201);
    write_duckdb_logical_type(writer, cast_type_id);
    if try_cast {
        writer.write_field_id(202);
        writer.output.push(1);
    }
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_between_expression_value(
    writer: &mut DuckDbBinaryWriter,
    negated: bool,
    write_input: impl FnOnce(&mut DuckDbBinaryWriter),
    write_lower: impl FnOnce(&mut DuckDbBinaryWriter),
    write_upper: impl FnOnce(&mut DuckDbBinaryWriter),
) {
    writer.output.push(1);
    write_duckdb_parsed_expression_base(writer, 19, if negated { 39 } else { 38 });
    writer.write_field_id(200);
    write_input(writer);
    writer.write_field_id(201);
    write_lower(writer);
    writer.write_field_id(202);
    write_upper(writer);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_default_expression_value(writer: &mut DuckDbBinaryWriter) {
    writer.output.push(1);
    write_duckdb_parsed_expression_base(writer, 8, 82);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_case_expression_value(
    writer: &mut DuckDbBinaryWriter,
    write_when: impl FnOnce(&mut DuckDbBinaryWriter),
    write_then: impl FnOnce(&mut DuckDbBinaryWriter),
    write_else: impl FnOnce(&mut DuckDbBinaryWriter),
) {
    writer.output.push(1);
    write_duckdb_parsed_expression_base(writer, 2, 150);
    writer.write_field_id(200);
    writer.write_unsigned_leb128(1);
    writer.write_field_id(100);
    write_when(writer);
    writer.write_field_id(101);
    write_then(writer);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
    writer.write_field_id(201);
    write_else(writer);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_collate_expression_value(
    writer: &mut DuckDbBinaryWriter,
    collation: &str,
    write_child: impl FnOnce(&mut DuckDbBinaryWriter),
) {
    writer.output.push(1);
    write_duckdb_parsed_expression_base(writer, 16, 230);
    writer.write_field_id(200);
    write_child(writer);
    writer.write_field_id(201);
    writer.write_string(collation);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_parameter_expression_value(writer: &mut DuckDbBinaryWriter, identifier: &str) {
    writer.output.push(1);
    write_duckdb_parsed_expression_base(writer, 15, 76);
    if !identifier.is_empty() {
        writer.write_field_id(200);
        writer.write_string(identifier);
    }
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_positional_reference_expression_value(writer: &mut DuckDbBinaryWriter, index: u64) {
    writer.output.push(1);
    write_duckdb_parsed_expression_base(writer, 18, 232);
    if index != 0 {
        writer.write_field_id(200);
        writer.write_unsigned_leb128(index);
    }
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_function_expression_extra_info(
    writer: &mut DuckDbBinaryWriter,
    function_name: &str,
    write_children: impl FnOnce(&mut DuckDbBinaryWriter),
    child_count: u64,
) {
    writer.write_field_id(204);
    writer.output.push(1);
    write_duckdb_parsed_expression_base(writer, 9, 140);
    if !function_name.is_empty() {
        writer.write_field_id(200);
        writer.write_string(function_name);
    }
    if child_count != 0 {
        writer.write_field_id(202);
        writer.write_unsigned_leb128(child_count);
        write_children(writer);
    }
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_parsed_expression_base(
    writer: &mut DuckDbBinaryWriter,
    expression_class: u64,
    expression_type: u64,
) {
    write_duckdb_parsed_expression_base_with_alias(writer, expression_class, expression_type, "");
}

fn write_duckdb_parsed_expression_base_with_alias(
    writer: &mut DuckDbBinaryWriter,
    expression_class: u64,
    expression_type: u64,
    alias: &str,
) {
    writer.write_field_id(100);
    writer.write_unsigned_leb128(expression_class);
    writer.write_field_id(101);
    writer.write_unsigned_leb128(expression_type);
    if !alias.is_empty() {
        writer.write_field_id(102);
        writer.write_string(alias);
    }
}

fn write_unsupported_duckdb_expression_extra_info(writer: &mut DuckDbBinaryWriter) {
    writer.write_field_id(204);
    writer.output.push(1);
    write_duckdb_parsed_expression_base(writer, 11, 200);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_i64_value_without_type(writer: &mut DuckDbBinaryWriter, value: i64) {
    writer.write_field_id(101);
    writer.output.push(0);
    writer.write_field_id(102);
    writer.write_signed_leb128(value);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_string_value_without_type(writer: &mut DuckDbBinaryWriter, value: &str) {
    writer.write_field_id(101);
    writer.output.push(0);
    writer.write_field_id(102);
    writer.write_string(value);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_struct_value(writer: &mut DuckDbBinaryWriter) {
    writer.write_field_id(100);
    write_duckdb_logical_type_with_extra_info(writer, 100, |writer| {
        write_duckdb_struct_extra_info(writer, 2, |writer| {
            write_duckdb_child_type(writer, "a", |writer| write_duckdb_logical_type(writer, 13));
            write_duckdb_child_type(writer, "b", |writer| write_duckdb_logical_type(writer, 25));
        });
    });
    writer.write_field_id(101);
    writer.output.push(0);
    writer.write_field_id(102);
    writer.write_field_id(100);
    writer.write_unsigned_leb128(2);
    write_duckdb_i64_value_without_type(writer, 7);
    write_duckdb_string_value_without_type(writer, "nested");
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_unsupported_duckdb_value(writer: &mut DuckDbBinaryWriter) {
    writer.write_field_id(100);
    write_duckdb_logical_type(writer, 39);
    writer.write_field_id(101);
    writer.output.push(0);
    writer.write_field_id(102);
    writer.write_string("123456789012345678901234567890");
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_extra_info_base(writer: &mut DuckDbBinaryWriter, info_type: u64) {
    writer.write_field_id(100);
    writer.write_unsigned_leb128(info_type);
}

fn write_duckdb_deleted_extra_info_modifier_values(
    writer: &mut DuckDbBinaryWriter,
    write_values: impl FnOnce(&mut DuckDbBinaryWriter),
    count: u64,
) {
    writer.write_field_id(102);
    writer.write_unsigned_leb128(count);
    write_values(writer);
}

fn write_duckdb_extension_type_info_with_scalar_payloads(writer: &mut DuckDbBinaryWriter) {
    writer.write_field_id(103);
    writer.output.push(1);
    writer.write_field_id(100);
    writer.write_unsigned_leb128(1);
    writer.write_field_id(100);
    write_duckdb_string_value(writer, "modifier-value");
    writer.write_field_id(101);
    writer.write_string("modifier-label");
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
    writer.write_field_id(101);
    writer.write_unsigned_leb128(2);
    writer.write_field_id(0);
    writer.write_string("threshold");
    writer.write_field_id(1);
    write_duckdb_f64_value(writer, 0.75);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
    writer.write_field_id(0);
    writer.write_string("shape");
    writer.write_field_id(1);
    write_duckdb_struct_value(writer);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_extension_type_info_with_unsupported_modifier(writer: &mut DuckDbBinaryWriter) {
    writer.write_field_id(103);
    writer.output.push(1);
    writer.write_field_id(100);
    writer.write_unsigned_leb128(1);
    writer.write_field_id(100);
    write_unsupported_duckdb_value(writer);
}

fn write_duckdb_decimal_extra_info(writer: &mut DuckDbBinaryWriter, width: u8, scale: u8) {
    write_duckdb_extra_info_base(writer, 2);
    writer.write_field_id(200);
    writer.write_unsigned_leb128(u64::from(width));
    writer.write_field_id(201);
    writer.write_unsigned_leb128(u64::from(scale));
}

fn write_duckdb_list_extra_info(
    writer: &mut DuckDbBinaryWriter,
    write_child_type: impl FnOnce(&mut DuckDbBinaryWriter),
) {
    write_duckdb_extra_info_base(writer, 4);
    writer.write_field_id(200);
    write_child_type(writer);
}

fn write_duckdb_struct_extra_info(
    writer: &mut DuckDbBinaryWriter,
    child_count: u64,
    write_children: impl FnOnce(&mut DuckDbBinaryWriter),
) {
    write_duckdb_extra_info_base(writer, 5);
    writer.write_field_id(200);
    writer.write_unsigned_leb128(child_count);
    write_children(writer);
}

fn write_duckdb_array_extra_info(
    writer: &mut DuckDbBinaryWriter,
    size: u32,
    write_child_type: impl FnOnce(&mut DuckDbBinaryWriter),
) {
    write_duckdb_extra_info_base(writer, 9);
    writer.write_field_id(200);
    write_child_type(writer);
    writer.write_field_id(201);
    writer.write_unsigned_leb128(u64::from(size));
}

fn write_duckdb_enum_extra_info(writer: &mut DuckDbBinaryWriter, values: &[&str]) {
    write_duckdb_extra_info_base(writer, 6);
    writer.write_field_id(200);
    writer.write_unsigned_leb128(values.len() as u64);
    writer.write_field_id(201);
    writer.write_unsigned_leb128(values.len() as u64);
    for value in values {
        writer.write_string(value);
    }
}

fn write_duckdb_aggregate_state_extra_info(
    writer: &mut DuckDbBinaryWriter,
    function_name: &str,
    write_return_type: impl FnOnce(&mut DuckDbBinaryWriter),
    argument_count: u64,
    write_argument_types: impl FnOnce(&mut DuckDbBinaryWriter),
) {
    write_duckdb_extra_info_base(writer, 8);
    writer.write_field_id(200);
    writer.write_string(function_name);
    writer.write_field_id(201);
    write_return_type(writer);
    writer.write_field_id(202);
    writer.write_unsigned_leb128(argument_count);
    write_argument_types(writer);
}

fn write_duckdb_any_extra_info(
    writer: &mut DuckDbBinaryWriter,
    write_target_type: impl FnOnce(&mut DuckDbBinaryWriter),
    cast_score: u64,
) {
    write_duckdb_extra_info_base(writer, 10);
    writer.write_field_id(200);
    write_target_type(writer);
    writer.write_field_id(201);
    writer.write_unsigned_leb128(cast_score);
}

fn write_duckdb_template_extra_info(writer: &mut DuckDbBinaryWriter, name: &str) {
    write_duckdb_extra_info_base(writer, 12);
    writer.write_field_id(200);
    writer.write_string(name);
}

fn write_duckdb_geo_extra_info(writer: &mut DuckDbBinaryWriter, crs_definition: &str) {
    write_duckdb_extra_info_base(writer, 13);
    writer.write_field_id(200);
    writer.write_field_id(100);
    writer.write_string(crs_definition);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_duckdb_invalid_extra_info(writer: &mut DuckDbBinaryWriter) {
    write_duckdb_extra_info_base(writer, 0);
}

fn write_duckdb_child_type(
    writer: &mut DuckDbBinaryWriter,
    name: &str,
    write_type: impl FnOnce(&mut DuckDbBinaryWriter),
) {
    writer.write_field_id(0);
    writer.write_string(name);
    writer.write_field_id(1);
    write_type(writer);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_test_data_chunk_wrapper(writer: &mut DuckDbBinaryWriter) {
    writer.output.push(1);
    writer.write_field_id(300);
    writer.write_field_id(100);
    writer.write_unsigned_leb128(3);
    writer.write_field_id(101);
    writer.write_unsigned_leb128(3);
    write_duckdb_logical_type(writer, 13);
    write_duckdb_logical_type(writer, 25);
    write_duckdb_logical_type(writer, 10);
    writer.write_field_id(102);
    writer.write_unsigned_leb128(3);

    writer.write_field_id(100);
    writer.output.push(1);
    writer.write_field_id(101);
    writer.write_unsigned_leb128(8);
    writer
        .output
        .extend_from_slice(&[0b0000_0101, 0, 0, 0, 0, 0, 0, 0]);
    writer.write_field_id(102);
    writer.write_unsigned_leb128(12);
    writer.output.extend_from_slice(&1i32.to_le_bytes());
    writer.output.extend_from_slice(&2i32.to_le_bytes());
    writer.output.extend_from_slice(&3i32.to_le_bytes());
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);

    writer.write_field_id(100);
    writer.output.push(0);
    writer.write_field_id(102);
    writer.write_unsigned_leb128(3);
    writer.write_string("one");
    writer.write_string("two");
    writer.write_string("three");
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);

    writer.write_field_id(90);
    writer.write_unsigned_leb128(2);
    writer.write_field_id(100);
    writer.output.push(0);
    writer.write_field_id(102);
    writer.write_unsigned_leb128(1);
    writer.output.push(1);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);

    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_test_nullable_string_data_chunk_wrapper(writer: &mut DuckDbBinaryWriter) {
    writer.output.push(1);
    writer.write_field_id(300);
    writer.write_field_id(100);
    writer.write_unsigned_leb128(3);
    writer.write_field_id(101);
    writer.write_unsigned_leb128(1);
    write_duckdb_logical_type(writer, 25);
    writer.write_field_id(102);
    writer.write_unsigned_leb128(1);
    write_flat_nullable_string_vector(writer, &[Some("version"), None, Some("data_path")]);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_test_decimal_data_chunk_wrapper(writer: &mut DuckDbBinaryWriter) {
    writer.output.push(1);
    writer.write_field_id(300);
    writer.write_field_id(100);
    writer.write_unsigned_leb128(2);
    writer.write_field_id(101);
    writer.write_unsigned_leb128(1);
    write_duckdb_logical_type_with_extra_info(writer, 21, |writer| {
        write_duckdb_decimal_extra_info(writer, 5, 2);
    });
    writer.write_field_id(102);
    writer.write_unsigned_leb128(1);

    writer.write_field_id(100);
    writer.output.push(0);
    writer.write_field_id(102);
    writer.write_unsigned_leb128(8);
    writer.output.extend_from_slice(&12345i32.to_le_bytes());
    writer.output.extend_from_slice(&(-7i32).to_le_bytes());
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);

    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_test_special_scalar_data_chunk_wrapper(writer: &mut DuckDbBinaryWriter) {
    writer.output.push(1);
    writer.write_field_id(300);
    writer.write_field_id(100);
    writer.write_unsigned_leb128(2);
    writer.write_field_id(101);
    writer.write_unsigned_leb128(6);
    write_duckdb_logical_type_with_extra_info(writer, 21, |writer| {
        write_duckdb_decimal_extra_info(writer, 20, 2);
    });
    write_duckdb_logical_type(writer, 50);
    write_duckdb_logical_type(writer, 49);
    write_duckdb_logical_type(writer, 54);
    write_duckdb_logical_type(writer, 27);
    write_duckdb_logical_type(writer, 34);
    writer.write_field_id(102);
    writer.write_unsigned_leb128(6);

    write_flat_i128_vector(writer, &[1_234_567_890_123_456_789, -700]);
    write_flat_i128_vector(writer, &[-9_223_372_036_854_775_809, 42]);
    write_flat_u128_vector(writer, &[18_446_744_073_709_551_620, 7]);
    write_flat_uuid_vector(
        writer,
        &[
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
            [
                16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
            ],
        ],
    );
    write_flat_interval_vector(writer, &[(14, 3, 4_005), (0, -2, -1_500_000)]);
    write_flat_i64_vector(
        writer,
        &[
            encode_duckdb_timetz(45_296_123_000, 3_600),
            encode_duckdb_timetz(1_000_000, -90),
        ],
    );

    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_flat_i32_vector(writer: &mut DuckDbBinaryWriter, values: &[i32]) {
    writer.write_field_id(100);
    writer.output.push(0);
    writer.write_field_id(102);
    writer.write_unsigned_leb128((values.len() * 4) as u64);
    for value in values {
        writer.output.extend_from_slice(&value.to_le_bytes());
    }
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_flat_i64_vector(writer: &mut DuckDbBinaryWriter, values: &[i64]) {
    writer.write_field_id(100);
    writer.output.push(0);
    writer.write_field_id(102);
    writer.write_unsigned_leb128((values.len() * 8) as u64);
    for value in values {
        writer.output.extend_from_slice(&value.to_le_bytes());
    }
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_flat_i128_vector(writer: &mut DuckDbBinaryWriter, values: &[i128]) {
    writer.write_field_id(100);
    writer.output.push(0);
    writer.write_field_id(102);
    writer.write_unsigned_leb128((values.len() * 16) as u64);
    for value in values {
        let lower = *value as u128 as u64;
        let upper = (*value >> 64) as i64;
        writer.output.extend_from_slice(&lower.to_le_bytes());
        writer.output.extend_from_slice(&upper.to_le_bytes());
    }
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_flat_u128_vector(writer: &mut DuckDbBinaryWriter, values: &[u128]) {
    writer.write_field_id(100);
    writer.output.push(0);
    writer.write_field_id(102);
    writer.write_unsigned_leb128((values.len() * 16) as u64);
    for value in values {
        let lower = *value as u64;
        let upper = (*value >> 64) as u64;
        writer.output.extend_from_slice(&lower.to_le_bytes());
        writer.output.extend_from_slice(&upper.to_le_bytes());
    }
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_flat_interval_vector(writer: &mut DuckDbBinaryWriter, values: &[(i32, i32, i64)]) {
    writer.write_field_id(100);
    writer.output.push(0);
    writer.write_field_id(102);
    writer.write_unsigned_leb128((values.len() * 16) as u64);
    for (months, days, micros) in values {
        writer.output.extend_from_slice(&months.to_le_bytes());
        writer.output.extend_from_slice(&days.to_le_bytes());
        writer.output.extend_from_slice(&micros.to_le_bytes());
    }
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_flat_uuid_vector(writer: &mut DuckDbBinaryWriter, values: &[[u8; 16]]) {
    writer.write_field_id(100);
    writer.output.push(0);
    writer.write_field_id(102);
    writer.write_unsigned_leb128((values.len() * 16) as u64);
    for value in values {
        let mut high = [0_u8; 8];
        high.copy_from_slice(&value[..8]);
        let upper = (u64::from_be_bytes(high) ^ (1_u64 << 63)) as i64;
        let mut low = [0_u8; 8];
        low.copy_from_slice(&value[8..]);
        let lower = u64::from_be_bytes(low);
        writer.output.extend_from_slice(&lower.to_le_bytes());
        writer.output.extend_from_slice(&upper.to_le_bytes());
    }
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn encode_duckdb_timetz(micros: i64, offset_seconds: i32) -> i64 {
    const OFFSET_BITS: u32 = 24;
    const MAX_OFFSET: i32 = 16 * 60 * 60 - 1;
    ((micros as u64) << OFFSET_BITS | u64::from((MAX_OFFSET - offset_seconds) as u32)) as i64
}

fn write_flat_bool_vector(writer: &mut DuckDbBinaryWriter, values: &[bool]) {
    writer.write_field_id(100);
    writer.output.push(0);
    writer.write_field_id(102);
    writer.write_unsigned_leb128(values.len() as u64);
    for value in values {
        writer.output.push(u8::from(*value));
    }
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_flat_string_vector(writer: &mut DuckDbBinaryWriter, values: &[&str]) {
    writer.write_field_id(100);
    writer.output.push(0);
    writer.write_field_id(102);
    writer.write_unsigned_leb128(values.len() as u64);
    for value in values {
        writer.write_string(value);
    }
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_flat_nullable_string_vector(writer: &mut DuckDbBinaryWriter, values: &[Option<&str>]) {
    writer.write_field_id(100);
    writer.output.push(1);
    writer.write_field_id(101);
    writer.write_unsigned_leb128(8);
    let mut validity = 0_u64;
    for (idx, value) in values.iter().enumerate() {
        if value.is_some() {
            validity |= 1_u64 << idx;
        }
    }
    writer.output.extend_from_slice(&validity.to_le_bytes());
    writer.write_field_id(102);
    writer.write_unsigned_leb128(values.len() as u64);
    for value in values {
        match value {
            Some(value) => writer.write_string(value),
            None => {
                writer.write_unsigned_leb128(1);
                writer.output.push(0x80);
            }
        }
    }
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_struct_vector(
    writer: &mut DuckDbBinaryWriter,
    child_count: u64,
    write_children: impl FnOnce(&mut DuckDbBinaryWriter),
) {
    writer.write_field_id(100);
    writer.output.push(0);
    writer.write_field_id(103);
    writer.write_unsigned_leb128(child_count);
    write_children(writer);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_list_vector(
    writer: &mut DuckDbBinaryWriter,
    list_size: u64,
    entries: &[(u64, u64)],
    write_child: impl FnOnce(&mut DuckDbBinaryWriter),
) {
    writer.write_field_id(100);
    writer.output.push(0);
    writer.write_field_id(104);
    writer.write_unsigned_leb128(list_size);
    writer.write_field_id(105);
    writer.write_unsigned_leb128(entries.len() as u64);
    for (offset, length) in entries {
        writer.write_field_id(100);
        writer.write_unsigned_leb128(*offset);
        writer.write_field_id(101);
        writer.write_unsigned_leb128(*length);
        writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
    }
    writer.write_field_id(106);
    write_child(writer);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_array_vector(
    writer: &mut DuckDbBinaryWriter,
    array_size: u64,
    write_child: impl FnOnce(&mut DuckDbBinaryWriter),
) {
    writer.write_field_id(100);
    writer.output.push(0);
    writer.write_field_id(103);
    writer.write_unsigned_leb128(array_size);
    writer.write_field_id(104);
    write_child(writer);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_sequence_i32_vector(writer: &mut DuckDbBinaryWriter, start: i64, increment: i64) {
    writer.write_field_id(90);
    writer.write_unsigned_leb128(4);
    writer.write_field_id(91);
    writer.write_signed_leb128(start);
    writer.write_field_id(92);
    writer.write_signed_leb128(increment);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_dictionary_string_vector(
    writer: &mut DuckDbBinaryWriter,
    selection: &[u32],
    dictionary: &[&str],
) {
    writer.write_field_id(90);
    writer.write_unsigned_leb128(3);
    writer.write_field_id(91);
    writer.write_unsigned_leb128((selection.len() * 4) as u64);
    for value in selection {
        writer.output.extend_from_slice(&value.to_le_bytes());
    }
    writer.write_field_id(92);
    writer.write_unsigned_leb128(dictionary.len() as u64);
    write_flat_string_vector(writer, dictionary);
}

fn write_duckdb_struct_ab_type(writer: &mut DuckDbBinaryWriter) {
    write_duckdb_logical_type_with_extra_info(writer, 100, |writer| {
        write_duckdb_struct_extra_info(writer, 2, |writer| {
            write_duckdb_child_type(writer, "a", |writer| write_duckdb_logical_type(writer, 13));
            write_duckdb_child_type(writer, "b", |writer| write_duckdb_logical_type(writer, 25));
        });
    });
}

fn write_duckdb_map_varchar_integer_type(writer: &mut DuckDbBinaryWriter) {
    write_duckdb_logical_type_with_extra_info(writer, 102, |writer| {
        write_duckdb_list_extra_info(writer, |writer| {
            write_duckdb_logical_type_with_extra_info(writer, 100, |writer| {
                write_duckdb_struct_extra_info(writer, 2, |writer| {
                    write_duckdb_child_type(writer, "key", |writer| {
                        write_duckdb_logical_type(writer, 25)
                    });
                    write_duckdb_child_type(writer, "value", |writer| {
                        write_duckdb_logical_type(writer, 13)
                    });
                });
            });
        });
    });
}

fn write_test_nested_data_chunk_wrapper(writer: &mut DuckDbBinaryWriter) {
    writer.output.push(1);
    writer.write_field_id(300);
    writer.write_field_id(100);
    writer.write_unsigned_leb128(2);
    writer.write_field_id(101);
    writer.write_unsigned_leb128(4);
    write_duckdb_struct_ab_type(writer);
    write_duckdb_logical_type_with_extra_info(writer, 101, |writer| {
        write_duckdb_list_extra_info(writer, |writer| write_duckdb_logical_type(writer, 13));
    });
    write_duckdb_map_varchar_integer_type(writer);
    write_duckdb_logical_type_with_extra_info(writer, 108, |writer| {
        write_duckdb_array_extra_info(writer, 2, |writer| write_duckdb_logical_type(writer, 10));
    });
    writer.write_field_id(102);
    writer.write_unsigned_leb128(4);

    write_struct_vector(writer, 2, |writer| {
        write_flat_i32_vector(writer, &[1, 2]);
        write_flat_string_vector(writer, &["x", "y"]);
    });
    write_list_vector(writer, 3, &[(0, 2), (2, 1)], |writer| {
        write_flat_i32_vector(writer, &[10, 20, 30]);
    });
    write_list_vector(writer, 3, &[(0, 2), (2, 1)], |writer| {
        write_struct_vector(writer, 2, |writer| {
            write_flat_string_vector(writer, &["a", "b", "c"]);
            write_flat_i32_vector(writer, &[1, 2, 3]);
        });
    });
    write_array_vector(writer, 2, |writer| {
        write_flat_bool_vector(writer, &[true, false, false, true]);
    });

    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn write_test_compressed_data_chunk_wrapper(writer: &mut DuckDbBinaryWriter) {
    writer.output.push(1);
    writer.write_field_id(300);
    writer.write_field_id(100);
    writer.write_unsigned_leb128(4);
    writer.write_field_id(101);
    writer.write_unsigned_leb128(2);
    write_duckdb_logical_type(writer, 13);
    write_duckdb_logical_type(writer, 25);
    writer.write_field_id(102);
    writer.write_unsigned_leb128(2);

    write_sequence_i32_vector(writer, 10, 2);
    write_dictionary_string_vector(writer, &[1, 2, 0, 1], &["a", "b", "c"]);

    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);
}

fn test_schema_field(
    id: i64,
    name: &str,
    ducklake_type: DuckLakeType,
    children: Vec<SchemaField>,
) -> SchemaField {
    SchemaField {
        field_id: ColumnId(id),
        name: name.into(),
        type_name: ducklake_type.to_string(),
        ducklake_type,
        nulls_allowed: true,
        initial_default: None,
        column_order: id,
        children,
    }
}

fn test_table_schema(columns: Vec<SchemaField>) -> TableSchema {
    let by_field_id = columns
        .iter()
        .enumerate()
        .map(|(idx, field)| (field.field_id, vec![idx]))
        .collect::<HashMap<_, _>>();
    TableSchema {
        columns,
        by_field_id,
    }
}

#[test]
fn decodes_quack_response_binary_envelopes() {
    let connection = duckdb_response_bytes(
        duckdb_response_header(MessageType::ConnectionResponse, "conn-1", None),
        vec![
            DuckDbProperty::new(
                DuckDbPropertyDescriptor::new(
                    1,
                    "server_duckdb_version",
                    DuckDbPropertyKind::String,
                    true,
                ),
                DuckDbPropertyValue::String("v1.5.0".into()),
            ),
            DuckDbProperty::new(
                DuckDbPropertyDescriptor::new(
                    2,
                    "server_platform",
                    DuckDbPropertyKind::String,
                    true,
                ),
                DuckDbPropertyValue::String("osx_arm64".into()),
            ),
            DuckDbProperty::new(
                DuckDbPropertyDescriptor::new(3, "quack_version", DuckDbPropertyKind::Idx, true),
                DuckDbPropertyValue::Idx(QUACK_VERSION),
            ),
        ],
    );
    let QuackResponse::Connection(response) =
        QuackResponse::from_duckdb_binary(&connection).unwrap()
    else {
        panic!("expected connection response");
    };
    assert_eq!(response.header.connection_id, "conn-1");
    assert_eq!(response.header.client_query_id, None);
    assert_eq!(response.server_duckdb_version, "v1.5.0");
    assert_eq!(response.server_platform, "osx_arm64");
    assert_eq!(response.quack_version, QUACK_VERSION);

    let success = duckdb_response_bytes(
        duckdb_response_header(MessageType::SuccessResponse, "conn-1", Some(4)),
        Vec::new(),
    );
    let QuackResponse::Success(response) = QuackResponse::from_duckdb_binary(&success).unwrap()
    else {
        panic!("expected success response");
    };
    assert_eq!(response.header.client_query_id, Some(4));
    assert_eq!(QuackQueryResult::empty().column_names, Vec::<String>::new());
    assert_eq!(QuackQueryResult::empty().column_types, Vec::<String>::new());
    assert!(QuackQueryResult::empty().rows.is_empty());

    let error = duckdb_response_bytes(
        duckdb_response_header(MessageType::ErrorResponse, "", None),
        vec![DuckDbProperty::new(
            DuckDbPropertyDescriptor::new(1, "message", DuckDbPropertyKind::String, true),
            DuckDbPropertyValue::String("Authentication failed".into()),
        )],
    );
    let QuackResponse::Error(response) = QuackResponse::from_duckdb_binary(&error).unwrap() else {
        panic!("expected error response");
    };
    assert_eq!(response.message, "Authentication failed");

    let fetch = duckdb_response_bytes(
        duckdb_response_header(MessageType::FetchResponse, "conn-1", None),
        vec![DuckDbProperty::new(
            DuckDbPropertyDescriptor::new(2, "batch_index", DuckDbPropertyKind::OptionalIdx, false),
            DuckDbPropertyValue::OptionalIdx(Some(5)),
        )],
    );
    let QuackResponse::Fetch(response) = QuackResponse::from_duckdb_binary(&fetch).unwrap() else {
        panic!("expected fetch response");
    };
    assert_eq!(response.batch_index, Some(5));
    assert!(response.results.is_empty());
}

#[test]
fn decodes_quack_prepare_response_without_duckdb_result_payloads() {
    let mut body = DuckDbBinaryWriter::default();
    body.write_field_id(1);
    body.write_unsigned_leb128(2);
    write_duckdb_logical_type(&mut body, 13);
    write_duckdb_logical_type(&mut body, 25);
    body.write_field_id(2);
    body.write_unsigned_leb128(2);
    body.write_string("answer");
    body.write_string("label");
    body.write_field_id(3);
    body.output.push(1);
    body.write_field_id(5);
    body.write_signed_leb128(0);
    body.write_unsigned_leb128(42);
    body.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);

    let mut writer = DuckDbBinaryWriter::default();
    writer
        .write_object(&duckdb_response_header(
            MessageType::PrepareResponse,
            "conn-1",
            None,
        ))
        .unwrap();
    writer.output.extend_from_slice(&body.finish());

    let QuackResponse::Prepare(response) =
        QuackResponse::from_duckdb_binary(&writer.finish()).unwrap()
    else {
        panic!("expected prepare response");
    };
    assert_eq!(response.result_types, vec!["INTEGER", "VARCHAR"]);
    assert_eq!(response.result_names, vec!["answer", "label"]);
    assert!(response.needs_more_fetch);
    assert!(response.results.is_empty());
    assert_eq!(response.result_uuid, 42);
}

#[test]
fn decodes_quack_prepare_response_without_result_types() {
    let mut body = DuckDbBinaryWriter::default();
    body.write_field_id(2);
    body.write_unsigned_leb128(1);
    body.write_string("answer");
    body.write_field_id(3);
    body.output.push(1);
    body.write_field_id(5);
    body.write_signed_leb128(0);
    body.write_unsigned_leb128(42);
    body.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);

    let mut writer = DuckDbBinaryWriter::default();
    writer
        .write_object(&duckdb_response_header(
            MessageType::PrepareResponse,
            "conn-1",
            None,
        ))
        .unwrap();
    writer.output.extend_from_slice(&body.finish());

    let QuackResponse::Prepare(response) =
        QuackResponse::from_duckdb_binary(&writer.finish()).unwrap()
    else {
        panic!("expected prepare response");
    };
    assert_eq!(response.result_types, Vec::<String>::new());
    assert_eq!(response.result_names, vec!["answer"]);
    assert!(response.needs_more_fetch);
    assert!(response.results.is_empty());
    assert_eq!(response.result_uuid, 42);
}

#[test]
fn decodes_quack_logical_type_extra_info_metadata() {
    let mut body = DuckDbBinaryWriter::default();
    body.write_field_id(1);
    body.write_unsigned_leb128(11);
    write_duckdb_logical_type_with_extra_info(&mut body, 21, |writer| {
        write_duckdb_decimal_extra_info(writer, 12, 2);
    });
    write_duckdb_logical_type_with_extra_info(&mut body, 101, |writer| {
        write_duckdb_list_extra_info(writer, |writer| write_duckdb_logical_type(writer, 13));
    });
    write_duckdb_logical_type_with_extra_info(&mut body, 100, |writer| {
        write_duckdb_struct_extra_info(writer, 2, |writer| {
            write_duckdb_child_type(writer, "a", |writer| write_duckdb_logical_type(writer, 13));
            write_duckdb_child_type(writer, "b", |writer| write_duckdb_logical_type(writer, 25));
        });
    });
    write_duckdb_logical_type_with_extra_info(&mut body, 102, |writer| {
        write_duckdb_list_extra_info(writer, |writer| {
            write_duckdb_logical_type_with_extra_info(writer, 100, |writer| {
                write_duckdb_struct_extra_info(writer, 2, |writer| {
                    write_duckdb_child_type(writer, "key", |writer| {
                        write_duckdb_logical_type(writer, 25)
                    });
                    write_duckdb_child_type(writer, "value", |writer| {
                        write_duckdb_logical_type(writer, 13)
                    });
                });
            });
        });
    });
    write_duckdb_logical_type_with_extra_info(&mut body, 108, |writer| {
        write_duckdb_array_extra_info(writer, 3, |writer| write_duckdb_logical_type(writer, 10));
    });
    write_duckdb_logical_type_with_extra_info(&mut body, 104, |writer| {
        write_duckdb_enum_extra_info(writer, &["open", "closed"]);
    });
    write_duckdb_logical_type_with_extra_info(&mut body, 105, |writer| {
        write_duckdb_aggregate_state_extra_info(
            writer,
            "sum",
            |writer| write_duckdb_logical_type(writer, 14),
            1,
            |writer| write_duckdb_logical_type(writer, 13),
        );
    });
    write_duckdb_logical_type_with_extra_info(&mut body, 3, |writer| {
        write_duckdb_any_extra_info(writer, |writer| write_duckdb_logical_type(writer, 25), 7);
    });
    write_duckdb_logical_type_with_extra_info(&mut body, 5, |writer| {
        write_duckdb_template_extra_info(writer, "KEY_TYPE");
    });
    write_duckdb_logical_type_with_extra_info(&mut body, 60, |writer| {
        write_duckdb_geo_extra_info(writer, "EPSG:4326");
    });
    write_duckdb_logical_type_with_extra_info(&mut body, 25, |writer| {
        write_duckdb_invalid_extra_info(writer);
    });
    body.write_field_id(2);
    body.write_unsigned_leb128(11);
    for name in [
        "amount", "items", "record", "lookup", "flags", "state", "agg", "any", "template", "geom",
        "plain",
    ] {
        body.write_string(name);
    }
    body.write_field_id(5);
    body.write_signed_leb128(0);
    body.write_unsigned_leb128(0);
    body.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);

    let mut writer = DuckDbBinaryWriter::default();
    writer
        .write_object(&duckdb_response_header(
            MessageType::PrepareResponse,
            "conn-1",
            None,
        ))
        .unwrap();
    writer.output.extend_from_slice(&body.finish());

    let QuackResponse::Prepare(response) =
        QuackResponse::from_duckdb_binary(&writer.finish()).unwrap()
    else {
        panic!("expected prepare response");
    };
    assert_eq!(
        response.result_types,
        vec![
            "DECIMAL(12,2)",
            "INTEGER[]",
            "STRUCT(a INTEGER, b VARCHAR)",
            "MAP(VARCHAR, INTEGER)",
            "BOOLEAN[3]",
            "ENUM('open', 'closed')",
            "AGGREGATE_STATE<sum(INTEGER)::BIGINT>",
            "ANY",
            "KEY_TYPE",
            "GEOMETRY('EPSG:4326')",
            "VARCHAR",
        ]
    );
}

#[test]
fn decodes_quack_fetch_response_scalar_data_chunks() {
    let mut writer = DuckDbBinaryWriter::default();
    writer
        .write_object(&duckdb_response_header(
            MessageType::FetchResponse,
            "conn-1",
            None,
        ))
        .unwrap();
    writer.write_field_id(1);
    writer.write_unsigned_leb128(1);
    write_test_data_chunk_wrapper(&mut writer);
    writer.write_field_id(2);
    writer.write_unsigned_leb128(0);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);

    let QuackResponse::Fetch(response) =
        QuackResponse::from_duckdb_binary(&writer.finish()).unwrap()
    else {
        panic!("expected fetch response");
    };
    assert_eq!(response.batch_index, Some(0));
    assert_eq!(response.results.len(), 1);
    assert_eq!(
        response.results[0].column_types,
        vec!["INTEGER", "VARCHAR", "BOOLEAN"]
    );
    assert_eq!(
        response.results[0].rows,
        vec![
            vec![
                DuckDbValue::Int32(1),
                DuckDbValue::Utf8("one".into()),
                DuckDbValue::Boolean(true),
            ],
            vec![
                DuckDbValue::Null,
                DuckDbValue::Utf8("two".into()),
                DuckDbValue::Boolean(true),
            ],
            vec![
                DuckDbValue::Int32(3),
                DuckDbValue::Utf8("three".into()),
                DuckDbValue::Boolean(true),
            ],
        ]
    );
}

#[test]
fn decodes_quack_nullable_string_vector_null_placeholders() {
    let mut writer = DuckDbBinaryWriter::default();
    writer
        .write_object(&duckdb_response_header(
            MessageType::FetchResponse,
            "conn-1",
            None,
        ))
        .unwrap();
    writer.write_field_id(1);
    writer.write_unsigned_leb128(1);
    write_test_nullable_string_data_chunk_wrapper(&mut writer);
    writer.write_field_id(2);
    writer.write_unsigned_leb128(0);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);

    let QuackResponse::Fetch(response) =
        QuackResponse::from_duckdb_binary(&writer.finish()).unwrap()
    else {
        panic!("expected fetch response");
    };
    assert_eq!(response.results[0].column_types, vec!["VARCHAR"]);
    assert_eq!(
        response.results[0].rows,
        vec![
            vec![DuckDbValue::Utf8("version".into())],
            vec![DuckDbValue::Null],
            vec![DuckDbValue::Utf8("data_path".into())],
        ]
    );
}

#[test]
fn decodes_quack_decimal_data_chunk_values() {
    let mut writer = DuckDbBinaryWriter::default();
    writer
        .write_object(&duckdb_response_header(
            MessageType::FetchResponse,
            "conn-1",
            None,
        ))
        .unwrap();
    writer.write_field_id(1);
    writer.write_unsigned_leb128(1);
    write_test_decimal_data_chunk_wrapper(&mut writer);
    writer.write_field_id(2);
    writer.write_unsigned_leb128(0);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);

    let QuackResponse::Fetch(response) =
        QuackResponse::from_duckdb_binary(&writer.finish()).unwrap()
    else {
        panic!("expected fetch response");
    };
    assert_eq!(response.results[0].column_types, vec!["DECIMAL(5,2)"]);
    assert_eq!(
        response.results[0].rows,
        vec![
            vec![DuckDbValue::Decimal {
                value: 12345,
                width: 5,
                scale: 2
            }],
            vec![DuckDbValue::Decimal {
                value: -7,
                width: 5,
                scale: 2
            }],
        ]
    );
}

#[test]
fn decodes_quack_special_scalar_data_chunk_values() {
    let mut writer = DuckDbBinaryWriter::default();
    writer
        .write_object(&duckdb_response_header(
            MessageType::FetchResponse,
            "conn-1",
            None,
        ))
        .unwrap();
    writer.write_field_id(1);
    writer.write_unsigned_leb128(1);
    write_test_special_scalar_data_chunk_wrapper(&mut writer);
    writer.write_field_id(2);
    writer.write_unsigned_leb128(0);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);

    let QuackResponse::Fetch(response) =
        QuackResponse::from_duckdb_binary(&writer.finish()).unwrap()
    else {
        panic!("expected fetch response");
    };
    assert_eq!(
        response.results[0].column_types,
        vec![
            "DECIMAL(20,2)",
            "HUGEINT",
            "UHUGEINT",
            "UUID",
            "INTERVAL",
            "TIME_TZ",
        ]
    );
    assert_eq!(
        response.results[0].rows,
        vec![
            vec![
                DuckDbValue::Decimal {
                    value: 1_234_567_890_123_456_789,
                    width: 20,
                    scale: 2,
                },
                DuckDbValue::Int128(-9_223_372_036_854_775_809),
                DuckDbValue::UInt128(18_446_744_073_709_551_620),
                DuckDbValue::Uuid([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
                DuckDbValue::Interval {
                    months: 14,
                    days: 3,
                    micros: 4_005,
                },
                DuckDbValue::TimeTzMicros(encode_duckdb_timetz(45_296_123_000, 3_600)),
            ],
            vec![
                DuckDbValue::Decimal {
                    value: -700,
                    width: 20,
                    scale: 2,
                },
                DuckDbValue::Int128(42),
                DuckDbValue::UInt128(7),
                DuckDbValue::Uuid([
                    16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
                ]),
                DuckDbValue::Interval {
                    months: 0,
                    days: -2,
                    micros: -1_500_000,
                },
                DuckDbValue::TimeTzMicros(encode_duckdb_timetz(1_000_000, -90)),
            ],
        ]
    );
}

#[test]
fn decodes_quack_nested_data_chunk_values() {
    let mut writer = DuckDbBinaryWriter::default();
    writer
        .write_object(&duckdb_response_header(
            MessageType::FetchResponse,
            "conn-1",
            None,
        ))
        .unwrap();
    writer.write_field_id(1);
    writer.write_unsigned_leb128(1);
    write_test_nested_data_chunk_wrapper(&mut writer);
    writer.write_field_id(2);
    writer.write_unsigned_leb128(0);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);

    let QuackResponse::Fetch(response) =
        QuackResponse::from_duckdb_binary(&writer.finish()).unwrap()
    else {
        panic!("expected fetch response");
    };
    assert_eq!(
        response.results[0].column_types,
        vec![
            "STRUCT(a INTEGER, b VARCHAR)",
            "INTEGER[]",
            "MAP(VARCHAR, INTEGER)",
            "BOOLEAN[2]",
        ]
    );
    assert_eq!(
        response.results[0].rows,
        vec![
            vec![
                DuckDbValue::Struct(vec![
                    ("a".into(), DuckDbValue::Int32(1)),
                    ("b".into(), DuckDbValue::Utf8("x".into())),
                ]),
                DuckDbValue::List(vec![DuckDbValue::Int32(10), DuckDbValue::Int32(20)]),
                DuckDbValue::Map(vec![
                    (DuckDbValue::Utf8("a".into()), DuckDbValue::Int32(1)),
                    (DuckDbValue::Utf8("b".into()), DuckDbValue::Int32(2)),
                ]),
                DuckDbValue::Array(vec![
                    DuckDbValue::Boolean(true),
                    DuckDbValue::Boolean(false),
                ]),
            ],
            vec![
                DuckDbValue::Struct(vec![
                    ("a".into(), DuckDbValue::Int32(2)),
                    ("b".into(), DuckDbValue::Utf8("y".into())),
                ]),
                DuckDbValue::List(vec![DuckDbValue::Int32(30)]),
                DuckDbValue::Map(vec![
                    (DuckDbValue::Utf8("c".into()), DuckDbValue::Int32(3),)
                ]),
                DuckDbValue::Array(vec![
                    DuckDbValue::Boolean(false),
                    DuckDbValue::Boolean(true),
                ]),
            ],
        ]
    );
}

#[test]
fn builds_quack_inlined_record_batch_for_scalar_values() {
    let schema = test_table_schema(vec![
        test_schema_field(1, "id", DuckLakeType::Int32, Vec::new()),
        test_schema_field(2, "label", DuckLakeType::Varchar, Vec::new()),
        test_schema_field(3, "flag", DuckLakeType::Boolean, Vec::new()),
        test_schema_field(4, "payload", DuckLakeType::Blob, Vec::new()),
        test_schema_field(5, "created", DuckLakeType::Date, Vec::new()),
    ]);
    let rows = vec![
        vec![
            DuckDbValue::Int32(7),
            DuckDbValue::Utf8("seven".into()),
            DuckDbValue::Boolean(true),
            DuckDbValue::Blob(vec![1, 2, 3]),
            DuckDbValue::DateDays(20_000),
        ],
        vec![
            DuckDbValue::Null,
            DuckDbValue::Utf8("missing".into()),
            DuckDbValue::Boolean(false),
            DuckDbValue::Null,
            DuckDbValue::Null,
        ],
    ];

    let batch = quack_inlined_record_batch(&schema, &rows).unwrap().unwrap();
    assert_eq!(batch.num_rows(), 2);
    assert_eq!(batch.num_columns(), 5);

    let ids = batch
        .column(0)
        .as_any()
        .downcast_ref::<Int32Array>()
        .unwrap();
    assert_eq!(ids.value(0), 7);
    assert!(ids.is_null(1));
    let labels = batch
        .column(1)
        .as_any()
        .downcast_ref::<StringArray>()
        .unwrap();
    assert_eq!(labels.value(1), "missing");
    let flags = batch
        .column(2)
        .as_any()
        .downcast_ref::<BooleanArray>()
        .unwrap();
    assert!(flags.value(0));
    assert!(!flags.value(1));
    let payloads = batch
        .column(3)
        .as_any()
        .downcast_ref::<BinaryArray>()
        .unwrap();
    assert_eq!(payloads.value(0), &[1, 2, 3]);
    assert!(payloads.is_null(1));
    let dates = batch
        .column(4)
        .as_any()
        .downcast_ref::<Date32Array>()
        .unwrap();
    assert_eq!(dates.value(0), 20_000);
    assert!(dates.is_null(1));
}

#[test]
fn builds_quack_inlined_record_batch_for_special_scalar_values() {
    let uuid = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
    let schema = test_table_schema(vec![
        test_schema_field(
            1,
            "amount",
            DuckLakeType::Decimal {
                width: 20,
                scale: 2,
            },
            Vec::new(),
        ),
        test_schema_field(2, "signed", DuckLakeType::Int128, Vec::new()),
        test_schema_field(3, "unsigned", DuckLakeType::UInt128, Vec::new()),
        test_schema_field(4, "uuid", DuckLakeType::Uuid, Vec::new()),
        test_schema_field(5, "duration", DuckLakeType::Interval, Vec::new()),
        test_schema_field(6, "wall_clock", DuckLakeType::TimeTz, Vec::new()),
    ]);
    let rows = vec![
        vec![
            DuckDbValue::Decimal {
                value: 12345,
                width: 20,
                scale: 2,
            },
            DuckDbValue::Int128(-9_223_372_036_854_775_809),
            DuckDbValue::UInt128(18_446_744_073_709_551_620),
            DuckDbValue::Uuid(uuid),
            DuckDbValue::Interval {
                months: 14,
                days: 3,
                micros: 4_005,
            },
            DuckDbValue::TimeTzMicros(encode_duckdb_timetz(45_296_123_000, 3_600)),
        ],
        vec![
            DuckDbValue::Null,
            DuckDbValue::Null,
            DuckDbValue::Null,
            DuckDbValue::Null,
            DuckDbValue::Null,
            DuckDbValue::Null,
        ],
    ];

    let batch = quack_inlined_record_batch(&schema, &rows).unwrap().unwrap();
    assert_eq!(batch.num_rows(), 2);
    assert_eq!(batch.num_columns(), 6);

    let amounts = batch
        .column(0)
        .as_any()
        .downcast_ref::<Decimal128Array>()
        .unwrap();
    assert_eq!(amounts.value(0), 12345);
    assert!(amounts.is_null(1));

    let signed = batch
        .column(1)
        .as_any()
        .downcast_ref::<Decimal256Array>()
        .unwrap();
    assert_eq!(
        signed.value(0),
        i256::from_i128(-9_223_372_036_854_775_809_i128)
    );
    assert!(signed.is_null(1));

    let unsigned = batch
        .column(2)
        .as_any()
        .downcast_ref::<Decimal256Array>()
        .unwrap();
    assert_eq!(
        unsigned.value(0),
        i256::from_parts(18_446_744_073_709_551_620, 0)
    );
    assert!(unsigned.is_null(1));

    let uuids = batch
        .column(3)
        .as_any()
        .downcast_ref::<FixedSizeBinaryArray>()
        .unwrap();
    assert_eq!(uuids.value(0), &uuid);
    assert!(uuids.is_null(1));

    let durations = batch
        .column(4)
        .as_any()
        .downcast_ref::<IntervalMonthDayNanoArray>()
        .unwrap();
    assert_eq!(
        durations.value(0),
        IntervalMonthDayNano::new(14, 3, 4_005_000)
    );
    assert!(durations.is_null(1));

    let wall_clock = batch
        .column(5)
        .as_any()
        .downcast_ref::<StringArray>()
        .unwrap();
    assert_eq!(wall_clock.value(0), "12:34:56.123+01");
    assert!(wall_clock.is_null(1));
}

#[test]
fn builds_quack_inlined_record_batch_for_nested_values() {
    let nested_field = test_schema_field(
        1,
        "nested",
        DuckLakeType::Struct,
        vec![
            test_schema_field(2, "a", DuckLakeType::Int32, Vec::new()),
            test_schema_field(3, "b", DuckLakeType::Varchar, Vec::new()),
        ],
    );
    let list_field = test_schema_field(
        4,
        "items",
        DuckLakeType::List,
        vec![test_schema_field(
            5,
            "element",
            DuckLakeType::Int32,
            Vec::new(),
        )],
    );
    let map_field = test_schema_field(
        6,
        "lookup",
        DuckLakeType::Map,
        vec![
            test_schema_field(7, "key", DuckLakeType::Varchar, Vec::new()),
            test_schema_field(8, "value", DuckLakeType::Int32, Vec::new()),
        ],
    );
    let schema = test_table_schema(vec![nested_field, list_field, map_field]);
    let rows = vec![
        vec![
            DuckDbValue::Struct(vec![
                ("a".into(), DuckDbValue::Int32(1)),
                ("b".into(), DuckDbValue::Utf8("one".into())),
            ]),
            DuckDbValue::List(vec![DuckDbValue::Int32(10), DuckDbValue::Int32(20)]),
            DuckDbValue::Map(vec![
                (DuckDbValue::Utf8("x".into()), DuckDbValue::Int32(100)),
                (DuckDbValue::Utf8("y".into()), DuckDbValue::Int32(200)),
            ]),
        ],
        vec![
            DuckDbValue::Null,
            DuckDbValue::List(vec![]),
            DuckDbValue::Map(vec![]),
        ],
    ];

    let batch = quack_inlined_record_batch(&schema, &rows).unwrap().unwrap();
    assert_eq!(batch.num_rows(), 2);

    let nested = batch
        .column(0)
        .as_any()
        .downcast_ref::<StructArray>()
        .unwrap();
    assert!(nested.is_null(1));
    let nested_a = nested
        .column(0)
        .as_any()
        .downcast_ref::<Int32Array>()
        .unwrap();
    assert_eq!(nested_a.value(0), 1);
    let nested_b = nested
        .column(1)
        .as_any()
        .downcast_ref::<StringArray>()
        .unwrap();
    assert_eq!(nested_b.value(0), "one");

    let items = batch
        .column(1)
        .as_any()
        .downcast_ref::<ListArray>()
        .unwrap();
    assert_eq!(items.value_length(0), 2);
    assert_eq!(items.value_length(1), 0);
    let item_values = items
        .values()
        .as_any()
        .downcast_ref::<Int32Array>()
        .unwrap();
    assert_eq!(item_values.value(0), 10);
    assert_eq!(item_values.value(1), 20);

    let lookup = batch.column(2).as_any().downcast_ref::<MapArray>().unwrap();
    assert_eq!(lookup.value_offsets(), &[0, 2, 2]);
}

#[test]
fn builds_quack_inlined_record_batch_for_nested_duckdb_literal_strings() {
    let nested_field = test_schema_field(
        1,
        "nested",
        DuckLakeType::Struct,
        vec![
            test_schema_field(2, "a", DuckLakeType::Int32, Vec::new()),
            test_schema_field(3, "b", DuckLakeType::Varchar, Vec::new()),
        ],
    );
    let list_field = test_schema_field(
        4,
        "items",
        DuckLakeType::List,
        vec![test_schema_field(
            5,
            "element",
            DuckLakeType::Int32,
            Vec::new(),
        )],
    );
    let map_field = test_schema_field(
        6,
        "lookup",
        DuckLakeType::Map,
        vec![
            test_schema_field(7, "key", DuckLakeType::Varchar, Vec::new()),
            test_schema_field(8, "value", DuckLakeType::Int32, Vec::new()),
        ],
    );
    let schema = test_table_schema(vec![nested_field, list_field, map_field]);
    let rows = vec![
        vec![
            DuckDbValue::Utf8("{'a': 10, 'b': x}".into()),
            DuckDbValue::Utf8("[1, 2]".into()),
            DuckDbValue::Utf8("{left=100, right=200}".into()),
        ],
        vec![
            DuckDbValue::Utf8("{'a': 20, 'b': y}".into()),
            DuckDbValue::Utf8("[3]".into()),
            DuckDbValue::Utf8("{solo=300}".into()),
        ],
    ];

    let batch = quack_inlined_record_batch(&schema, &rows).unwrap().unwrap();
    assert_eq!(batch.num_rows(), 2);

    let nested = batch
        .column(0)
        .as_any()
        .downcast_ref::<StructArray>()
        .unwrap();
    let nested_a = nested
        .column(0)
        .as_any()
        .downcast_ref::<Int32Array>()
        .unwrap();
    let nested_b = nested
        .column(1)
        .as_any()
        .downcast_ref::<StringArray>()
        .unwrap();
    assert_eq!(nested_a.value(0), 10);
    assert_eq!(nested_b.value(0), "x");
    assert_eq!(nested_a.value(1), 20);
    assert_eq!(nested_b.value(1), "y");

    let items = batch
        .column(1)
        .as_any()
        .downcast_ref::<ListArray>()
        .unwrap();
    assert_eq!(items.value_offsets(), &[0, 2, 3]);
    let item_values = items
        .values()
        .as_any()
        .downcast_ref::<Int32Array>()
        .unwrap();
    assert_eq!(item_values.values(), &[1, 2, 3]);

    let lookup = batch.column(2).as_any().downcast_ref::<MapArray>().unwrap();
    assert_eq!(lookup.value_offsets(), &[0, 2, 3]);
    let keys = lookup
        .keys()
        .as_any()
        .downcast_ref::<StringArray>()
        .unwrap();
    let values = lookup
        .values()
        .as_any()
        .downcast_ref::<Int32Array>()
        .unwrap();
    assert_eq!(keys.value(0), "left");
    assert_eq!(values.value(0), 100);
    assert_eq!(keys.value(2), "solo");
    assert_eq!(values.value(2), 300);
}

#[test]
fn rejects_quack_inlined_record_batch_types_not_decoded_yet() {
    let unknown_schema = test_table_schema(vec![test_schema_field(
        1,
        "mystery",
        DuckLakeType::Unknown,
        Vec::new(),
    )]);
    let err = quack_inlined_record_batch(&unknown_schema, &[vec![DuckDbValue::Utf8("x".into())]])
        .unwrap_err();
    assert!(format!("{err}").contains("Inlined DuckLake type"));
}

#[test]
fn decodes_quack_sequence_and_dictionary_data_chunk_values() {
    let mut writer = DuckDbBinaryWriter::default();
    writer
        .write_object(&duckdb_response_header(
            MessageType::FetchResponse,
            "conn-1",
            None,
        ))
        .unwrap();
    writer.write_field_id(1);
    writer.write_unsigned_leb128(1);
    write_test_compressed_data_chunk_wrapper(&mut writer);
    writer.write_field_id(2);
    writer.write_unsigned_leb128(0);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);

    let QuackResponse::Fetch(response) =
        QuackResponse::from_duckdb_binary(&writer.finish()).unwrap()
    else {
        panic!("expected fetch response");
    };

    assert_eq!(response.results[0].column_types, vec!["INTEGER", "VARCHAR"]);
    assert_eq!(
        response.results[0].rows,
        vec![
            vec![DuckDbValue::Int32(10), DuckDbValue::Utf8("b".into())],
            vec![DuckDbValue::Int32(12), DuckDbValue::Utf8("c".into())],
            vec![DuckDbValue::Int32(14), DuckDbValue::Utf8("a".into())],
            vec![DuckDbValue::Int32(16), DuckDbValue::Utf8("b".into())],
        ]
    );
}

#[test]
fn decodes_extra_type_extension_info_and_deleted_modifiers() {
    let mut writer = DuckDbBinaryWriter::default();
    writer
        .write_object(&duckdb_response_header(
            MessageType::PrepareResponse,
            "conn-1",
            None,
        ))
        .unwrap();
    writer.write_field_id(1);
    writer.write_unsigned_leb128(1);
    write_duckdb_logical_type_with_extra_info(&mut writer, 21, |writer| {
        write_duckdb_extra_info_base(writer, 2);
        write_duckdb_deleted_extra_info_modifier_values(
            writer,
            |writer| write_duckdb_i64_value(writer, 13, 42),
            1,
        );
        write_duckdb_extension_type_info_with_scalar_payloads(writer);
        writer.write_field_id(200);
        writer.write_unsigned_leb128(9);
        writer.write_field_id(201);
        writer.write_unsigned_leb128(2);
    });
    writer.write_field_id(5);
    writer.write_hugeint(0);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);

    let QuackResponse::Prepare(response) =
        QuackResponse::from_duckdb_binary(&writer.finish()).unwrap()
    else {
        panic!("expected prepare response");
    };
    assert_eq!(response.result_types, vec!["DECIMAL(9,2)"]);

    let mut writer = DuckDbBinaryWriter::default();
    writer
        .write_object(&duckdb_response_header(
            MessageType::PrepareResponse,
            "conn-1",
            None,
        ))
        .unwrap();
    writer.write_field_id(1);
    writer.write_unsigned_leb128(1);
    write_duckdb_logical_type_with_extra_info(&mut writer, 21, |writer| {
        write_duckdb_extra_info_base(writer, 2);
        write_duckdb_extension_type_info_with_unsupported_modifier(writer);
    });

    let err = QuackResponse::from_duckdb_binary(&writer.finish()).unwrap_err();
    assert!(format!("{err}").contains("Quack DuckDB Value decoding"));
}

#[test]
fn decodes_integer_literal_extra_type_value() {
    let mut writer = DuckDbBinaryWriter::default();
    writer
        .write_object(&duckdb_response_header(
            MessageType::PrepareResponse,
            "conn-1",
            None,
        ))
        .unwrap();
    writer.write_field_id(1);
    writer.write_unsigned_leb128(1);
    write_duckdb_logical_type_with_extra_info(&mut writer, 38, |writer| {
        write_duckdb_extra_info_base(writer, 11);
        writer.write_field_id(200);
        write_duckdb_i64_value(writer, 14, 9_223_372_036_854_775_000);
    });
    writer.write_field_id(5);
    writer.write_hugeint(0);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);

    let QuackResponse::Prepare(response) =
        QuackResponse::from_duckdb_binary(&writer.finish()).unwrap()
    else {
        panic!("expected prepare response");
    };
    assert_eq!(response.result_types, vec!["BIGINT"]);
}

#[test]
fn decodes_unbound_type_info_type_expressions() {
    let mut writer = DuckDbBinaryWriter::default();
    writer
        .write_object(&duckdb_response_header(
            MessageType::PrepareResponse,
            "conn-1",
            None,
        ))
        .unwrap();
    writer.write_field_id(1);
    writer.write_unsigned_leb128(3);
    write_duckdb_logical_type_with_extra_info(&mut writer, 4, |writer| {
        write_duckdb_extra_info_base(writer, 7);
        write_duckdb_type_expression_extra_info(
            writer,
            "ducklake",
            "main",
            "label_type",
            |writer| {
                write_duckdb_constant_expression_value(writer, |writer| {
                    write_duckdb_i64_value(writer, 13, 42)
                });
                write_duckdb_constant_expression_value(writer, |writer| {
                    write_duckdb_string_value(writer, "v1")
                });
            },
            2,
        );
    });
    write_duckdb_logical_type_with_extra_info(&mut writer, 4, |writer| {
        write_duckdb_extra_info_base(writer, 7);
        writer.write_field_id(200);
        writer.write_string("legacy_type");
        writer.write_field_id(201);
        writer.write_string("ducklake");
        writer.write_field_id(202);
        writer.write_string("main");
        writer.write_field_id(203);
        writer.write_unsigned_leb128(1);
        write_duckdb_i64_value(writer, 13, 7);
    });
    write_duckdb_logical_type_with_extra_info(&mut writer, 4, |writer| {
        write_duckdb_extra_info_base(writer, 7);
        write_duckdb_type_expression_extra_info(
            writer,
            "",
            "",
            "STRUCT",
            |writer| {
                write_duckdb_type_expression_value(writer, "id", "", "", "INTEGER", |_| {}, 0);
                write_duckdb_type_expression_value(
                    writer,
                    "labels",
                    "",
                    "",
                    "LIST",
                    |writer| {
                        write_duckdb_type_expression_value(
                            writer,
                            "",
                            "",
                            "",
                            "VARCHAR",
                            |_| {},
                            0,
                        );
                    },
                    1,
                );
                write_duckdb_type_expression_value(
                    writer,
                    "weights",
                    "",
                    "",
                    "MAP",
                    |writer| {
                        write_duckdb_type_expression_value(
                            writer,
                            "",
                            "",
                            "",
                            "VARCHAR",
                            |_| {},
                            0,
                        );
                        write_duckdb_type_expression_value(
                            writer,
                            "",
                            "",
                            "",
                            "DECIMAL",
                            |writer| {
                                write_duckdb_constant_expression_value(writer, |writer| {
                                    write_duckdb_i64_value(writer, 13, 9)
                                });
                                write_duckdb_constant_expression_value(writer, |writer| {
                                    write_duckdb_i64_value(writer, 13, 2)
                                });
                            },
                            2,
                        );
                    },
                    2,
                );
            },
            3,
        );
    });
    writer.write_field_id(5);
    writer.write_hugeint(0);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);

    let QuackResponse::Prepare(response) =
        QuackResponse::from_duckdb_binary(&writer.finish()).unwrap()
    else {
        panic!("expected prepare response");
    };
    assert_eq!(
        response.result_types,
        vec![
            "ducklake.main.label_type(42, 'v1')",
            "ducklake.main.legacy_type(7)",
            "STRUCT(id INTEGER, labels VARCHAR[], weights MAP(VARCHAR, DECIMAL(9, 2)))"
        ]
    );
}

#[test]
fn decodes_unbound_type_info_common_parsed_expressions() {
    let mut writer = DuckDbBinaryWriter::default();
    writer
        .write_object(&duckdb_response_header(
            MessageType::PrepareResponse,
            "conn-1",
            None,
        ))
        .unwrap();
    writer.write_field_id(1);
    writer.write_unsigned_leb128(1);
    write_duckdb_logical_type_with_extra_info(&mut writer, 4, |writer| {
        write_duckdb_extra_info_base(writer, 7);
        write_duckdb_function_expression_extra_info(
            writer,
            "macro_type",
            |writer| {
                write_duckdb_cast_expression_value(writer, 13, false, |writer| {
                    write_duckdb_constant_expression_value(writer, |writer| {
                        write_duckdb_string_value(writer, "42")
                    });
                });
                write_duckdb_comparison_expression_value(
                    writer,
                    25,
                    |writer| write_duckdb_column_ref_expression_value(writer, &["tbl", "col"]),
                    |writer| {
                        write_duckdb_constant_expression_value(writer, |writer| {
                            write_duckdb_string_value(writer, "x")
                        });
                    },
                );
                write_duckdb_operator_expression_value(
                    writer,
                    13,
                    |writer| write_duckdb_column_ref_expression_value(writer, &["flag"]),
                    1,
                );
                write_duckdb_collate_expression_value(writer, "en_US", |writer| {
                    write_duckdb_case_expression_value(
                        writer,
                        |writer| {
                            write_duckdb_between_expression_value(
                                writer,
                                false,
                                |writer| {
                                    write_duckdb_column_ref_expression_value(writer, &["score"])
                                },
                                |writer| {
                                    write_duckdb_constant_expression_value(writer, |writer| {
                                        write_duckdb_i64_value(writer, 13, 1)
                                    });
                                },
                                |writer| {
                                    write_duckdb_constant_expression_value(writer, |writer| {
                                        write_duckdb_i64_value(writer, 13, 9)
                                    });
                                },
                            );
                        },
                        |writer| {
                            write_duckdb_constant_expression_value(writer, |writer| {
                                write_duckdb_string_value(writer, "ok")
                            });
                        },
                        write_duckdb_default_expression_value,
                    );
                });
                write_duckdb_positional_reference_expression_value(writer, 2);
                write_duckdb_parameter_expression_value(writer, "named");
            },
            6,
        );
    });
    writer.write_field_id(5);
    writer.write_hugeint(0);
    writer.write_field_id(DUCKDB_MESSAGE_TERMINATOR_FIELD_ID);

    let QuackResponse::Prepare(response) =
        QuackResponse::from_duckdb_binary(&writer.finish()).unwrap()
    else {
        panic!("expected prepare response");
    };
    assert_eq!(
        response.result_types,
        vec![
            "macro_type(CAST('42' AS INTEGER), tbl.col = 'x', NOT flag, CASE WHEN score BETWEEN 1 AND 9 THEN 'ok' ELSE DEFAULT END COLLATE en_US, #2, $named)"
        ]
    );
}

#[test]
fn decodes_duckdb_scalar_decimal_and_type_values() {
    let mut writer = DuckDbBinaryWriter::default();
    write_duckdb_decimal_value(&mut writer, 4, 2, -1234);
    write_duckdb_decimal_value(&mut writer, 20, 3, 1_234_567_890_123_456_789);
    write_duckdb_interval_value(&mut writer, 14, -3, 4_005);
    write_duckdb_interval_value(&mut writer, 0, 0, 0);
    write_duckdb_type_value(&mut writer, |writer| {
        write_duckdb_logical_type_with_extra_info(writer, 21, |writer| {
            write_duckdb_decimal_extra_info(writer, 9, 2);
        });
    });

    let buffer = writer.finish();
    let mut reader = DuckDbBinaryReader::new(&buffer);
    assert_eq!(
        reader.read_duckdb_value().unwrap(),
        DuckDbValue::Decimal {
            value: -1234,
            width: 4,
            scale: 2,
        }
    );
    assert_eq!(
        reader.read_duckdb_value().unwrap(),
        DuckDbValue::Decimal {
            value: 1_234_567_890_123_456_789,
            width: 20,
            scale: 3,
        }
    );
    assert_eq!(
        reader.read_duckdb_value().unwrap(),
        DuckDbValue::Interval {
            months: 14,
            days: -3,
            micros: 4_005,
        }
    );
    assert_eq!(
        reader.read_duckdb_value().unwrap(),
        DuckDbValue::Interval {
            months: 0,
            days: 0,
            micros: 0,
        }
    );
    assert_eq!(
        reader.read_duckdb_value().unwrap(),
        DuckDbValue::Type("DECIMAL(9,2)".into())
    );
    reader.finish().unwrap();
}

#[test]
fn rejects_quack_binary_result_payloads_that_still_need_decoders() {
    let mut writer = DuckDbBinaryWriter::default();
    writer
        .write_object(&duckdb_response_header(
            MessageType::FetchResponse,
            "conn-1",
            None,
        ))
        .unwrap();
    writer.write_field_id(1);
    writer.write_unsigned_leb128(1);
    writer.output.push(1);
    writer.write_field_id(300);
    writer.write_field_id(100);
    writer.write_unsigned_leb128(1);
    writer.write_field_id(101);
    writer.write_unsigned_leb128(1);
    write_duckdb_logical_type(&mut writer, 13);
    writer.write_field_id(102);
    writer.write_unsigned_leb128(1);
    writer.write_field_id(90);
    writer.write_unsigned_leb128(1);

    let err = QuackResponse::from_duckdb_binary(&writer.finish()).unwrap_err();
    assert!(format!("{err}").contains("Quack DuckDB compressed Vector response decoding"));

    let mut writer = DuckDbBinaryWriter::default();
    writer
        .write_object(&duckdb_response_header(
            MessageType::PrepareResponse,
            "conn-1",
            None,
        ))
        .unwrap();
    writer.write_field_id(1);
    writer.write_unsigned_leb128(1);
    write_duckdb_logical_type_with_extra_info(&mut writer, 4, |writer| {
        write_duckdb_extra_info_base(writer, 7);
        write_unsupported_duckdb_expression_extra_info(writer);
    });

    let err = QuackResponse::from_duckdb_binary(&writer.finish()).unwrap_err();
    assert!(format!("{err}").contains("Quack DuckDB UnboundTypeInfo"));
}

#[test]
fn models_quack_connection_flow_without_server_roundtrip() {
    let uri = QuackUri::parse_with_ssl("quack://localhost:9494", false).unwrap();
    let response = ConnectionResponse::new("conn-1", "v1.5.0", "osx_arm64", QUACK_VERSION);
    let connection = QuackConnection::from_response(uri.clone(), response).unwrap();
    assert_eq!(connection.uri(), &uri);
    assert_eq!(connection.connection_id(), "conn-1");
    assert_eq!(
        QuackHttpClient::new(uri.clone()).endpoint(),
        "http://localhost:9494/quack"
    );

    let prepare = connection
        .prepare_request("SELECT 42")
        .with_client_query_id(99);
    assert_eq!(prepare.message_type(), MessageType::PrepareRequest);
    assert_eq!(
        prepare.expected_response_type(),
        MessageType::PrepareResponse
    );
    assert_eq!(prepare.header().connection_id, "conn-1");
    assert_eq!(prepare.header().client_query_id, Some(99));

    let prepare_response = QuackResponse::Prepare(PrepareResponse {
        header: MessageHeader::new(MessageType::PrepareResponse, "conn-1"),
        result_types: vec!["INTEGER".into()],
        result_names: vec!["answer".into()],
        needs_more_fetch: false,
        results: Vec::new(),
        result_uuid: 123,
    });
    prepare.validate_response(&prepare_response).unwrap();

    let mismatch = QuackResponse::Success(SuccessResponse {
        header: MessageHeader::new(MessageType::SuccessResponse, "conn-1"),
    });
    let mismatch_err = prepare.validate_response(&mismatch).unwrap_err();
    assert!(format!("{mismatch_err}").contains("expected PREPARE_RESPONSE message"));

    let server_error = QuackResponse::Error(ErrorResponse {
        header: MessageHeader::new(MessageType::ErrorResponse, "conn-1"),
        message: "Authentication failed".into(),
    });
    let server_err = prepare.validate_response(&server_error).unwrap_err();
    assert!(format!("{server_err}").contains("Authentication failed"));

    let fetch = connection.fetch_request(123);
    assert_eq!(fetch.message_type(), MessageType::FetchRequest);
    assert_eq!(fetch.expected_response_type(), MessageType::FetchResponse);

    let disconnect = connection.disconnect_request();
    assert_eq!(disconnect.message_type(), MessageType::DisconnectMessage);
    assert_eq!(
        disconnect.expected_response_type(),
        MessageType::SuccessResponse
    );
}

#[test]
fn caches_quack_connection_across_catalog_clones() {
    let catalog = QuackCatalog::try_new("quack:localhost", "secret-token").unwrap();
    let rt = tokio::runtime::Builder::new_current_thread()
        .build()
        .unwrap();
    rt.block_on(async {
        let connection = QuackConnection::from_response(
            catalog.uri().clone(),
            ConnectionResponse::new("conn-1", "v1.5.0", "osx_arm64", QUACK_VERSION),
        )
        .unwrap();
        *catalog.connection.lock().await = Some(connection);

        let cloned = catalog.clone();
        assert_eq!(
            cloned
                .connection
                .lock()
                .await
                .as_ref()
                .map(QuackConnection::connection_id),
            Some("conn-1")
        );

        cloned.clear_cached_connection().await;
        assert!(catalog.connection.lock().await.is_none());
        cloned.disconnect().await.unwrap();
    });
}

#[test]
fn rejects_incompatible_quack_protocol_versions() {
    assert_eq!(
        negotiate_quack_version(1, 3, 2, "v1.5.0", "osx_arm64").unwrap(),
        2
    );

    let err =
        negotiate_quack_version(QUACK_VERSION, QUACK_VERSION, 2, "v1.6.0", "linux").unwrap_err();
    assert!(format!("{err}").contains("server protocol version 2"));

    let invalid_range = negotiate_quack_version(3, 1, 2, "v1.5.0", "osx_arm64").unwrap_err();
    assert!(format!("{invalid_range}").contains("invalid Quack client version range"));

    let empty_connection = ConnectionResponse::new("", "v1.5.0", "osx_arm64", QUACK_VERSION);
    let uri = QuackUri::parse("quack:localhost").unwrap();
    let empty_err = QuackConnection::from_response(uri, empty_connection).unwrap_err();
    assert!(format!("{empty_err}").contains("empty connection id"));
}

#[test]
fn validates_quack_append_commit_inputs_before_server_roundtrip() {
    let catalog = QuackCatalog::try_new("quack:localhost", "secret-token").unwrap();
    let rt = tokio::runtime::Builder::new_current_thread()
        .build()
        .unwrap();
    let err = rt
        .block_on(catalog.commit_append_files(Vec::new()))
        .unwrap_err();
    assert!(format!("{err}").contains("received no files"));

    let err = rt
        .block_on(catalog.commit_append_files(vec![
            quack_append_file(10, SnapshotId(1)),
            AppendFile {
                table_id: TableId(2),
                ..quack_append_file(11, SnapshotId(1))
            },
        ]))
        .unwrap_err();
    assert!(format!("{err}").contains("span multiple tables"));

    let err = rt
        .block_on(catalog.commit_delete_files(Vec::new()))
        .unwrap_err();
    assert!(format!("{err}").contains("received no files"));

    let err = rt
        .block_on(catalog.commit_delete_files(vec![
            quack_delete_file(10, SnapshotId(1), 10, 1, None),
            DeleteFileCommit {
                table_id: TableId(2),
                ..quack_delete_file(11, SnapshotId(1), 10, 1, None)
            },
        ]))
        .unwrap_err();
    assert!(format!("{err}").contains("span multiple tables"));

    let err = rt
        .block_on(catalog.commit_table_changes(TableChangeCommit {
            table_id: TableId(1),
            expected_snapshot: SnapshotId(1),
            append_files: Vec::new(),
            delete_files: Vec::new(),
            inlined_file_deletions: Vec::new(),
            inlined_data_appends: Vec::new(),
            inlined_data_deletions: Vec::new(),
            flushed_inlined_data: Vec::new(),
            flushed_inlined_file_deletions: Vec::new(),
            changes_made: "updated_table:1".into(),
            commit_message: "datafusion update".into(),
        }))
        .unwrap_err();
    assert!(format!("{err}").contains("received no changes"));

    let err = rt
        .block_on(catalog.commit_table_changes(TableChangeCommit {
            table_id: TableId(1),
            expected_snapshot: SnapshotId(1),
            append_files: vec![AppendFile {
                table_id: TableId(2),
                ..quack_append_file(10, SnapshotId(1))
            }],
            delete_files: Vec::new(),
            inlined_file_deletions: Vec::new(),
            inlined_data_appends: Vec::new(),
            inlined_data_deletions: Vec::new(),
            flushed_inlined_data: Vec::new(),
            flushed_inlined_file_deletions: Vec::new(),
            changes_made: "updated_table:1".into(),
            commit_message: "datafusion update".into(),
        }))
        .unwrap_err();
    assert!(format!("{err}").contains("span multiple tables"));

    let err = rt
        .block_on(catalog.commit_table_changes(TableChangeCommit {
            table_id: TableId(1),
            expected_snapshot: SnapshotId(1),
            append_files: Vec::new(),
            delete_files: Vec::new(),
            inlined_file_deletions: vec![quack_inlined_file_deletion_commit(
                10,
                SnapshotId(1),
                Vec::new(),
            )],
            inlined_data_appends: Vec::new(),
            inlined_data_deletions: Vec::new(),
            flushed_inlined_data: Vec::new(),
            flushed_inlined_file_deletions: Vec::new(),
            changes_made: "updated_table:1".into(),
            commit_message: "datafusion update".into(),
        }))
        .unwrap_err();
    assert!(format!("{err}").contains("has no rows"));
}

#[test]
fn builds_quack_scheduled_file_removal_sql() {
    assert_eq!(quack_remove_scheduled_files_sql(&[]), None);
    assert_eq!(
        quack_remove_scheduled_files_sql(&[10, 42]),
        Some(
            "DELETE FROM ducklake_files_scheduled_for_deletion WHERE data_file_id IN (10, 42)"
                .to_string()
        )
    );
    let data_file_cleanup_sql =
        crate::embedded_maintenance::data_files_unreferenced_by_snapshots_sql(&[3, 5]);
    assert!(data_file_cleanup_sql.contains("df.table_id IN (3, 5) OR "));
    assert_eq!(quack_sql_bool(true), "TRUE");
    assert_eq!(quack_sql_bool(false), "FALSE");
    assert_eq!(
        crate::join_cleanup_path_components("/tmp/lake", "main/table.parquet"),
        "/tmp/lake/main/table.parquet"
    );
    assert_eq!(
        crate::join_cleanup_path_components("/tmp/lake/", "/main/table.parquet"),
        "/tmp/lake/main/table.parquet"
    );
}

#[test]
fn quack_inlined_append_numeric_scalar_helpers_follow_reference_mapping() {
    let tiny = test_schema_field(1, "tiny", DuckLakeType::Int8, Vec::new());
    assert_eq!(
        inlined_column_type(&tiny, InlinedSqlDialect::Quack).unwrap(),
        "TINYINT"
    );
    let values = Int8Array::from(vec![Some(-8), None]);
    assert_eq!(quack_inlined_sql_value(&tiny, &values, 0).unwrap(), "-8");
    assert_eq!(quack_inlined_sql_value(&tiny, &values, 1).unwrap(), "NULL");

    let small = test_schema_field(2, "small", DuckLakeType::Int16, Vec::new());
    assert_eq!(
        inlined_column_type(&small, InlinedSqlDialect::Quack).unwrap(),
        "SMALLINT"
    );
    let values = Int16Array::from(vec![Some(-1_600), None]);
    assert_eq!(
        quack_inlined_sql_value(&small, &values, 0).unwrap(),
        "-1600"
    );
    assert_eq!(quack_inlined_sql_value(&small, &values, 1).unwrap(), "NULL");

    let utiny = test_schema_field(3, "utiny", DuckLakeType::UInt8, Vec::new());
    assert_eq!(
        inlined_column_type(&utiny, InlinedSqlDialect::Quack).unwrap(),
        "UTINYINT"
    );
    let values = UInt8Array::from(vec![Some(250), None]);
    assert_eq!(quack_inlined_sql_value(&utiny, &values, 0).unwrap(), "250");
    assert_eq!(quack_inlined_sql_value(&utiny, &values, 1).unwrap(), "NULL");

    let usmall = test_schema_field(4, "usmall", DuckLakeType::UInt16, Vec::new());
    assert_eq!(
        inlined_column_type(&usmall, InlinedSqlDialect::Quack).unwrap(),
        "USMALLINT"
    );
    let values = UInt16Array::from(vec![Some(60_000), None]);
    assert_eq!(
        quack_inlined_sql_value(&usmall, &values, 0).unwrap(),
        "60000"
    );
    assert_eq!(
        quack_inlined_sql_value(&usmall, &values, 1).unwrap(),
        "NULL"
    );

    let uint = test_schema_field(5, "uint", DuckLakeType::UInt32, Vec::new());
    assert_eq!(
        inlined_column_type(&uint, InlinedSqlDialect::Quack).unwrap(),
        "UINTEGER"
    );
    let values = UInt32Array::from(vec![Some(4_000_000_000), None]);
    assert_eq!(
        quack_inlined_sql_value(&uint, &values, 0).unwrap(),
        "4000000000"
    );
    assert_eq!(quack_inlined_sql_value(&uint, &values, 1).unwrap(), "NULL");

    let ubig = test_schema_field(6, "ubig", DuckLakeType::UInt64, Vec::new());
    assert_eq!(
        inlined_column_type(&ubig, InlinedSqlDialect::Quack).unwrap(),
        "UBIGINT"
    );
    let values = UInt64Array::from(vec![Some(u64::MAX), None]);
    assert_eq!(
        quack_inlined_sql_value(&ubig, &values, 0).unwrap(),
        "CAST('18446744073709551615' AS UBIGINT)"
    );
    assert_eq!(quack_inlined_sql_value(&ubig, &values, 1).unwrap(), "NULL");

    let huge = test_schema_field(7, "huge", DuckLakeType::Int128, Vec::new());
    assert_eq!(
        inlined_column_type(&huge, InlinedSqlDialect::Quack).unwrap(),
        "HUGEINT"
    );
    let values = Decimal256Array::from(vec![
        Some(i256::from_i128(
            -123_456_789_012_345_678_901_234_567_890_i128,
        )),
        None,
    ])
    .with_precision_and_scale(39, 0)
    .unwrap();
    assert_eq!(
        quack_inlined_sql_value(&huge, &values, 0).unwrap(),
        "CAST('-123456789012345678901234567890' AS HUGEINT)"
    );
    assert_eq!(quack_inlined_sql_value(&huge, &values, 1).unwrap(), "NULL");

    let uhuge = test_schema_field(8, "uhuge", DuckLakeType::UInt128, Vec::new());
    assert_eq!(
        inlined_column_type(&uhuge, InlinedSqlDialect::Quack).unwrap(),
        "UHUGEINT"
    );
    let uhuge_value = "340282366920938463463374607431768211455"
        .parse::<i256>()
        .unwrap();
    let values = Decimal256Array::from(vec![Some(uhuge_value), None])
        .with_precision_and_scale(39, 0)
        .unwrap();
    assert_eq!(
        quack_inlined_sql_value(&uhuge, &values, 0).unwrap(),
        "CAST('340282366920938463463374607431768211455' AS UHUGEINT)"
    );
    assert_eq!(quack_inlined_sql_value(&uhuge, &values, 1).unwrap(), "NULL");

    let real32 = test_schema_field(6, "real32", DuckLakeType::Float32, Vec::new());
    assert_eq!(
        inlined_column_type(&real32, InlinedSqlDialect::Quack).unwrap(),
        "FLOAT"
    );
    let values = Float32Array::from(vec![Some(1.5), None]);
    assert_eq!(quack_inlined_sql_value(&real32, &values, 0).unwrap(), "1.5");
    assert_eq!(
        quack_inlined_sql_value(&real32, &values, 1).unwrap(),
        "NULL"
    );

    let real64 = test_schema_field(7, "real64", DuckLakeType::Float64, Vec::new());
    assert_eq!(
        inlined_column_type(&real64, InlinedSqlDialect::Quack).unwrap(),
        "DOUBLE"
    );
    let values = Float64Array::from(vec![Some(-2.25), None]);
    assert_eq!(
        quack_inlined_sql_value(&real64, &values, 0).unwrap(),
        "-2.25"
    );
    assert_eq!(
        quack_inlined_sql_value(&real64, &values, 1).unwrap(),
        "NULL"
    );

    let decimal = test_schema_field(
        10,
        "decimal_value",
        DuckLakeType::Decimal { width: 8, scale: 2 },
        Vec::new(),
    );
    assert_eq!(
        inlined_column_type(&decimal, InlinedSqlDialect::Quack).unwrap(),
        "DECIMAL(8,2)"
    );
    let values = Decimal128Array::from(vec![Some(12_345), None])
        .with_precision_and_scale(8, 2)
        .unwrap();
    assert_eq!(
        quack_inlined_sql_value(&decimal, &values, 0).unwrap(),
        "CAST('123.45' AS DECIMAL(8,2))"
    );
    assert_eq!(
        quack_inlined_sql_value(&decimal, &values, 1).unwrap(),
        "NULL"
    );

    let wide_decimal = test_schema_field(
        11,
        "wide_decimal",
        DuckLakeType::Decimal {
            width: 41,
            scale: 2,
        },
        Vec::new(),
    );
    assert!(matches!(
        inlined_column_type(&wide_decimal, InlinedSqlDialect::Quack),
        Err(StorageError::Unsupported(message)) if message.contains("DECIMAL width 41")
    ));

    let day = test_schema_field(12, "day_value", DuckLakeType::Date, Vec::new());
    assert_eq!(
        inlined_column_type(&day, InlinedSqlDialect::Quack).unwrap(),
        "DATE"
    );
    let values = Date32Array::from(vec![Some(1), None]);
    assert_eq!(
        quack_inlined_sql_value(&day, &values, 0).unwrap(),
        "CAST('1970-01-02' AS DATE)"
    );
    assert_eq!(quack_inlined_sql_value(&day, &values, 1).unwrap(), "NULL");

    let time = test_schema_field(13, "time_value", DuckLakeType::Time, Vec::new());
    assert_eq!(
        inlined_column_type(&time, InlinedSqlDialect::Quack).unwrap(),
        "TIME"
    );
    let values = Time64MicrosecondArray::from(vec![Some(45_296_123_456), None]);
    assert_eq!(
        quack_inlined_sql_value(&time, &values, 0).unwrap(),
        "CAST('12:34:56.123456' AS TIME)"
    );
    assert_eq!(quack_inlined_sql_value(&time, &values, 1).unwrap(), "NULL");

    let time_ns = test_schema_field(14, "time_ns_value", DuckLakeType::TimeNs, Vec::new());
    assert_eq!(
        inlined_column_type(&time_ns, InlinedSqlDialect::Quack).unwrap(),
        "TIME_NS"
    );
    let values = Time64NanosecondArray::from(vec![Some(45_296_123_456_789), None]);
    assert_eq!(
        quack_inlined_sql_value(&time_ns, &values, 0).unwrap(),
        "CAST('12:34:56.123456789' AS TIME_NS)"
    );
    assert_eq!(
        quack_inlined_sql_value(&time_ns, &values, 1).unwrap(),
        "NULL"
    );

    let timestamp_s = test_schema_field(15, "timestamp_s", DuckLakeType::TimestampS, Vec::new());
    assert_eq!(
        inlined_column_type(&timestamp_s, InlinedSqlDialect::Quack).unwrap(),
        "TIMESTAMP_S"
    );
    let values = TimestampSecondArray::from(vec![Some(1), None]);
    assert_eq!(
        quack_inlined_sql_value(&timestamp_s, &values, 0).unwrap(),
        "CAST('1970-01-01 00:00:01' AS TIMESTAMP_S)"
    );
    assert_eq!(
        quack_inlined_sql_value(&timestamp_s, &values, 1).unwrap(),
        "NULL"
    );

    let timestamp_ms = test_schema_field(16, "timestamp_ms", DuckLakeType::TimestampMs, Vec::new());
    assert_eq!(
        inlined_column_type(&timestamp_ms, InlinedSqlDialect::Quack).unwrap(),
        "TIMESTAMP_MS"
    );
    let values = TimestampMillisecondArray::from(vec![Some(1_234), None]);
    assert_eq!(
        quack_inlined_sql_value(&timestamp_ms, &values, 0).unwrap(),
        "CAST('1970-01-01 00:00:01.234' AS TIMESTAMP_MS)"
    );
    assert_eq!(
        quack_inlined_sql_value(&timestamp_ms, &values, 1).unwrap(),
        "NULL"
    );

    let timestamp = test_schema_field(17, "timestamp_value", DuckLakeType::Timestamp, Vec::new());
    assert_eq!(
        inlined_column_type(&timestamp, InlinedSqlDialect::Quack).unwrap(),
        "TIMESTAMP"
    );
    let values = TimestampMicrosecondArray::from(vec![Some(1_234_567), None]);
    assert_eq!(
        quack_inlined_sql_value(&timestamp, &values, 0).unwrap(),
        "CAST('1970-01-01 00:00:01.234567' AS TIMESTAMP)"
    );
    assert_eq!(
        quack_inlined_sql_value(&timestamp, &values, 1).unwrap(),
        "NULL"
    );

    let timestamp_ns = test_schema_field(18, "timestamp_ns", DuckLakeType::TimestampNs, Vec::new());
    assert_eq!(
        inlined_column_type(&timestamp_ns, InlinedSqlDialect::Quack).unwrap(),
        "TIMESTAMP_NS"
    );
    let values = TimestampNanosecondArray::from(vec![Some(1_234_567_890), None]);
    assert_eq!(
        quack_inlined_sql_value(&timestamp_ns, &values, 0).unwrap(),
        "CAST('1970-01-01 00:00:01.234567890' AS TIMESTAMP_NS)"
    );
    assert_eq!(
        quack_inlined_sql_value(&timestamp_ns, &values, 1).unwrap(),
        "NULL"
    );

    let timestamp_tz = test_schema_field(19, "timestamp_tz", DuckLakeType::TimestampTz, Vec::new());
    assert_eq!(
        inlined_column_type(&timestamp_tz, InlinedSqlDialect::Quack).unwrap(),
        "TIMESTAMP_TZ"
    );
    let values = TimestampMicrosecondArray::from(vec![Some(1_234_567), None]).with_timezone("UTC");
    assert_eq!(
        quack_inlined_sql_value(&timestamp_tz, &values, 0).unwrap(),
        "CAST('1970-01-01 00:00:01.234567+00' AS TIMESTAMP_TZ)"
    );
    assert_eq!(
        quack_inlined_sql_value(&timestamp_tz, &values, 1).unwrap(),
        "NULL"
    );

    let timetz = test_schema_field(20, "timetz_value", DuckLakeType::TimeTz, Vec::new());
    assert_eq!(
        inlined_column_type(&timetz, InlinedSqlDialect::Quack).unwrap(),
        "TIME_TZ"
    );
    let values = StringArray::from(vec![Some("12:34:56+02"), None]);
    assert_eq!(
        quack_inlined_sql_value(&timetz, &values, 0).unwrap(),
        "CAST('12:34:56+02' AS TIME_TZ)"
    );
    assert_eq!(
        quack_inlined_sql_value(&timetz, &values, 1).unwrap(),
        "NULL"
    );

    let interval = test_schema_field(21, "interval_value", DuckLakeType::Interval, Vec::new());
    assert_eq!(
        inlined_column_type(&interval, InlinedSqlDialect::Quack).unwrap(),
        "INTERVAL"
    );
    let values = IntervalMonthDayNanoArray::from(vec![
        Some(IntervalMonthDayNano::new(14, 3, 4_005_000)),
        None,
    ]);
    assert_eq!(
        quack_inlined_sql_value(&interval, &values, 0).unwrap(),
        "CAST('14 months 3 days 4005 microseconds' AS INTERVAL)"
    );
    assert_eq!(
        quack_inlined_sql_value(&interval, &values, 1).unwrap(),
        "NULL"
    );

    let blob = test_schema_field(8, "bytes", DuckLakeType::Blob, Vec::new());
    assert_eq!(
        inlined_column_type(&blob, InlinedSqlDialect::Quack).unwrap(),
        "BLOB"
    );
    let blob_bytes = vec![0x00_u8, 0xff, 0x10];
    let values = BinaryArray::from(vec![Some(blob_bytes.as_slice()), None]);
    assert_eq!(
        quack_inlined_sql_value(&blob, &values, 0).unwrap(),
        "from_hex('00ff10')"
    );
    assert_eq!(quack_inlined_sql_value(&blob, &values, 1).unwrap(), "NULL");

    let uuid = test_schema_field(9, "uuid_value", DuckLakeType::Uuid, Vec::new());
    assert_eq!(
        inlined_column_type(&uuid, InlinedSqlDialect::Quack).unwrap(),
        "UUID"
    );
    let uuid_bytes = [
        0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
        0xff,
    ];
    let uuid_values = vec![Some(uuid_bytes.as_slice()), None];
    let values =
        FixedSizeBinaryArray::try_from_sparse_iter_with_size(uuid_values.into_iter(), 16).unwrap();
    assert_eq!(
        quack_inlined_sql_value(&uuid, &values, 0).unwrap(),
        "CAST('00112233-4455-6677-8899-aabbccddeeff' AS UUID)"
    );
    assert_eq!(quack_inlined_sql_value(&uuid, &values, 1).unwrap(), "NULL");
}

fn quack_append_file(data_file_id: i64, expected_snapshot: SnapshotId) -> AppendFile {
    TestAppendFile {
        table_id: TableId(1),
        data_file_id,
        expected_snapshot,
        path: format!("quack-append-{data_file_id}.parquet"),
        record_count: 2,
        file_size_bytes: 25,
        column_stats: vec![test_file_column_stats(
            data_file_id,
            TableId(1),
            ColumnId(2),
            Some(8),
            2,
            0,
            "10",
            "20",
        )],
        partition_id: Some(PartitionId(3)),
        partition_values: vec![test_file_partition_value(data_file_id, TableId(1), 0, "4")],
    }
    .into_commit()
}

#[test]
fn staged_append_files_sql_matches_reference_shape() {
    let file = quack_append_file(10, SnapshotId(1));
    let planned_files = [crate::AppendFilePlan {
        row_id_start: Some(20),
        begin_snapshot: 2,
    }];
    let sql = quack_staged_append_files_sql(QuackStagedAppendFilesSql {
        files: std::slice::from_ref(&file),
        planned_files: &planned_files,
        snapshot_ids: crate::commit::CommitSnapshotIds {
            snapshot_id: 1,
            schema_version: 5,
            next_catalog_id: 100,
            next_file_id: 10,
        },
        data_path: "/tmp/ducklake-data/",
        metadata_schema_name: "ducklake_meta",
    })
    .unwrap();

    assert!(sql.contains("CREATE TEMPORARY TABLE IF NOT EXISTS ducklake_staged_commit"));
    assert!(sql.contains("TRUNCATE ducklake_staged_data_file;"));
    assert!(sql.contains("INSERT INTO ducklake_staged_commit VALUES (NULL, 'datafusion append', NULL, 1, '/tmp/ducklake-data/', '/', 100, 10);"));
    assert!(sql.contains("INSERT INTO ducklake_staged_data_file VALUES (0, 1, 0, 'quack-append-10.parquet', false, 'parquet', 2, 25, NULL, 20, 3, NULL, NULL, NULL, 2, NULL);"));
    assert!(sql.contains("INSERT INTO ducklake_staged_data_file_column_stats VALUES (0, 1, 2, 8, TRUE, 2, TRUE, 0, TRUE, '10', TRUE, '20', TRUE, FALSE, TRUE, NULL);"));
    assert!(sql.contains("INSERT INTO ducklake_staged_data_file_partition VALUES (0, 0, '4');"));
    assert!(sql.contains("SELECT * FROM ducklake_commit('ducklake_meta', 5"));
    assert!(!sql.contains("RETURNING"));
}

#[test]
fn quack_commit_mode_parses_user_options() {
    assert_eq!(
        "auto".parse::<QuackCommitMode>().unwrap(),
        QuackCommitMode::Auto
    );
    assert_eq!(
        "client-side".parse::<QuackCommitMode>().unwrap(),
        QuackCommitMode::Client
    );
    assert_eq!(
        "server_required".parse::<QuackCommitMode>().unwrap(),
        QuackCommitMode::Server
    );
    assert!("unknown".parse::<QuackCommitMode>().is_err());
}

#[test]
fn staged_delete_files_sql_matches_reference_shape() {
    let file = quack_delete_file(11, SnapshotId(1), 10, 2, Some(DeleteFileId(9)));
    let mut target_data_paths = HashMap::new();
    target_data_paths.insert(10, "main/numbers/data-10.parquet".to_string());

    let sql = quack_staged_delete_files_sql(QuackStagedDeleteFilesSql {
        files: std::slice::from_ref(&file),
        snapshot_ids: crate::commit::CommitSnapshotIds {
            snapshot_id: 1,
            schema_version: 5,
            next_catalog_id: 100,
            next_file_id: 11,
        },
        new_snapshot: SnapshotId(2),
        data_path: "/tmp/ducklake-data/",
        metadata_schema_name: "ducklake_meta",
        target_data_paths: &target_data_paths,
    })
    .unwrap();

    assert!(sql.contains("TRUNCATE ducklake_staged_delete_file;"));
    assert!(sql.contains("INSERT INTO ducklake_staged_commit VALUES (NULL, 'datafusion delete', NULL, 1, '/tmp/ducklake-data/', '/', 100, 11);"));
    assert!(sql.contains("INSERT INTO ducklake_staged_delete_file VALUES (1, 'main/numbers/data-10.parquet', 10, 'quack-delete-11.parquet', 'parquet', 2, 12, 4, NULL, 2, NULL, 'REGULAR', TRUE, 9, NULL, NULL);"));
    assert!(sql.contains("SELECT * FROM ducklake_commit('ducklake_meta', 5"));
    assert!(!sql.contains("RETURNING"));
}

#[test]
fn staged_append_inlined_data_sql_matches_reference_shape() {
    let inlined_schema = test_table_schema(vec![test_schema_field(
        2,
        "value",
        DuckLakeType::Int64,
        Vec::new(),
    )]);
    let inlined_arrow_schema = Arc::new(Schema::new(vec![Field::new(
        "value",
        DataType::Int64,
        true,
    )]));
    let inlined_batch = RecordBatch::try_new(
        inlined_arrow_schema,
        vec![Arc::new(Int64Array::from(vec![Some(40), Some(50)]))],
    )
    .unwrap();

    let sql = quack_staged_append_inlined_data_sql(QuackStagedAppendInlinedDataSql {
        append: &InlinedDataAppendCommit {
            table_id: TableId(1),
            expected_snapshot: SnapshotId(4),
            schema_version: 5,
            schema: inlined_schema,
            batches: vec![inlined_batch],
            column_stats: Vec::new(),
            rows_appended: 2,
            changes_made: "inserted_into_table:1".into(),
            commit_message: "datafusion inlined append".into(),
        },
        row_id_start: 20,
        snapshot_ids: crate::commit::CommitSnapshotIds {
            snapshot_id: 4,
            schema_version: 5,
            next_catalog_id: 100,
            next_file_id: 12,
        },
        data_path: "/tmp/ducklake-data/",
        metadata_schema_name: "ducklake_meta",
    })
    .unwrap();

    assert!(sql.contains("INSERT INTO ducklake_staged_commit VALUES (NULL, 'datafusion inlined append', NULL, 4, '/tmp/ducklake-data/', '/', 100, 12);"));
    assert!(sql.contains("INSERT INTO ducklake_staged_inlined_data VALUES (1, FALSE);"));
    assert!(sql.contains("INSERT INTO ducklake_staged_inlined_row VALUES (1, 0, NULL, '(40)');"));
    assert!(sql.contains("INSERT INTO ducklake_staged_inlined_row VALUES (1, 1, NULL, '(50)');"));
    assert!(sql.contains("SELECT * FROM ducklake_commit('ducklake_meta', 5"));
}

#[test]
fn staged_table_changes_sql_matches_reference_shape() {
    let append_file = quack_append_file(12, SnapshotId(4));
    let delete_file = quack_delete_file(13, SnapshotId(4), 10, 1, None);
    let inlined_schema = test_table_schema(vec![test_schema_field(
        2,
        "value",
        DuckLakeType::Int64,
        Vec::new(),
    )]);
    let inlined_arrow_schema = Arc::new(Schema::new(vec![Field::new(
        "value",
        DataType::Int64,
        true,
    )]));
    let inlined_batch = RecordBatch::try_new(
        inlined_arrow_schema,
        vec![Arc::new(Int64Array::from(vec![Some(40), Some(50)]))],
    )
    .unwrap();
    let changes = TableChangeCommit {
        table_id: TableId(1),
        expected_snapshot: SnapshotId(4),
        append_files: vec![append_file],
        delete_files: vec![delete_file],
        inlined_file_deletions: vec![quack_inlined_file_deletion_commit(
            12,
            SnapshotId(4),
            vec![3],
        )],
        inlined_data_appends: vec![InlinedDataAppend {
            schema_version: 5,
            schema: inlined_schema,
            batches: vec![inlined_batch],
            column_stats: Vec::new(),
            row_ids: Some(vec![20, 21]),
            rows_appended: 2,
        }],
        inlined_data_deletions: vec![InlinedDataDeletionCommit {
            table_name: "ducklake_inlined_data_1_5".into(),
            row_ids: vec![7],
            rows_deleted: 1,
        }],
        flushed_inlined_data: Vec::new(),
        flushed_inlined_file_deletions: Vec::new(),
        changes_made: "updated_table:1".into(),
        commit_message: "datafusion update".into(),
    };
    let snapshot_plan = crate::TableChangeSnapshotPlan {
        new_snapshot: SnapshotId(5),
        new_next_file_id: 14,
        next_row_id_after_appends: Some(22),
        total_appended_record_count: 4,
        total_allocated_row_id_count: 2,
        total_appended_file_size_bytes: 25,
        total_declared_deleted_rows: 2,
        append_files: vec![crate::AppendFilePlan {
            row_id_start: Some(18),
            begin_snapshot: 5,
        }],
        inlined_data_appends: vec![crate::InlinedDataAppendPlan { row_id_start: 20 }],
    };
    let mut target_data_paths = HashMap::new();
    target_data_paths.insert(10, "main/numbers/data-10.parquet".to_string());

    let sql = quack_staged_table_changes_sql(QuackStagedTableChangesSql {
        changes: &changes,
        snapshot_plan: &snapshot_plan,
        snapshot_ids: crate::commit::CommitSnapshotIds {
            snapshot_id: 4,
            schema_version: 5,
            next_catalog_id: 100,
            next_file_id: 12,
        },
        data_path: "/tmp/ducklake-data/",
        metadata_schema_name: "ducklake_meta",
        target_data_paths: &target_data_paths,
    })
    .unwrap();

    assert!(sql.contains("INSERT INTO ducklake_staged_commit VALUES (NULL, 'datafusion update', NULL, 4, '/tmp/ducklake-data/', '/', 100, 12);"));
    assert!(sql.contains("INSERT INTO ducklake_staged_data_file VALUES (0, 1, 0, 'quack-append-12.parquet', false, 'parquet', 2, 25, NULL, 18, 3, NULL, NULL, NULL, 5, NULL);"));
    assert!(sql.contains("INSERT INTO ducklake_staged_delete_file VALUES (1, 'main/numbers/data-10.parquet', 10, 'quack-delete-13.parquet', 'parquet', 1, 12, 4, NULL, 5, NULL, 'REGULAR', FALSE, NULL, NULL, NULL);"));
    assert!(sql.contains("INSERT INTO ducklake_staged_inlined_data VALUES (1, TRUE);"));
    assert!(sql.contains("INSERT INTO ducklake_staged_inlined_row VALUES (1, 0, 20, '(40)');"));
    assert!(sql.contains("INSERT INTO ducklake_staged_inlined_row VALUES (1, 1, 21, '(50)');"));
    assert!(sql.contains(
        "INSERT INTO ducklake_staged_inlined_delete VALUES (1, 'ducklake_inlined_data_1_5', 7);"
    ));
    assert!(sql.contains("INSERT INTO ducklake_staged_inlined_file_delete VALUES (1, 12, 3);"));
    assert!(sql.contains("SELECT * FROM ducklake_commit('ducklake_meta', 5"));
}

fn quack_delete_file(
    delete_file_id: i64,
    expected_snapshot: SnapshotId,
    data_file_id: i64,
    rows_deleted: i64,
    overwritten_delete_file_id: Option<DeleteFileId>,
) -> DeleteFileCommit {
    TestDeleteFile {
        table_id: TableId(1),
        delete_file_id,
        expected_snapshot,
        data_file_id,
        path: format!("quack-delete-{delete_file_id}.parquet"),
        rows_deleted,
        file_size_bytes: 12,
        footer_size: Some(4),
        overwritten_delete_file_id,
    }
    .into_commit()
}

fn quack_inlined_file_deletion_commit(
    data_file_id: i64,
    expected_snapshot: SnapshotId,
    row_ids: Vec<i64>,
) -> InlinedFileDeletionCommit {
    test_inlined_file_deletion(TableId(1), data_file_id, expected_snapshot, row_ids)
}

#[test]
fn live_quack_concurrent_append_commits_return_one_conflict_when_enabled() {
    let live_enabled = std::env::var("DUCKLAKE_QUACK_LIVE_TEST")
        .ok()
        .is_some_and(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "on"));
    if !live_enabled {
        eprintln!(
            "skipping live Quack concurrency test; set DUCKLAKE_QUACK_LIVE_TEST=1 and run scripts/start-quack-server.sh"
        );
        return;
    }
    let _live_guard = QUACK_LIVE_TEST_LOCK.lock().unwrap();

    let uri =
        std::env::var("DUCKLAKE_QUACK_TEST_URI").unwrap_or_else(|_| "quack:localhost".to_string());
    let token = std::env::var("DUCKLAKE_QUACK_TEST_TOKEN").unwrap_or_else(|_| "asdf".to_string());
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
        let setup = QuackCatalog::try_new(uri.clone(), token.clone()).unwrap();
        for table in [
            "ducklake_table_stats",
            "ducklake_table_column_stats",
            "ducklake_file_partition_value",
            "ducklake_file_column_stats",
            "ducklake_delete_file",
            "ducklake_data_file",
            "ducklake_column",
            "ducklake_snapshot_changes",
            "ducklake_snapshot",
        ] {
            setup
                .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                .await
                .unwrap();
        }
        setup
            .query_sql(
                "CREATE TABLE ducklake_snapshot( \
                         snapshot_id BIGINT, snapshot_time TIMESTAMP, schema_version BIGINT, \
                         next_catalog_id BIGINT, next_file_id BIGINT \
                     )",
            )
            .await
            .unwrap();
        setup
            .query_sql(
                "CREATE TABLE ducklake_snapshot_changes( \
                         snapshot_id BIGINT, changes_made VARCHAR, author VARCHAR, \
                         commit_message VARCHAR, commit_extra_info VARCHAR \
                     )",
            )
            .await
            .unwrap();
        setup
            .query_sql(
                "CREATE TABLE ducklake_data_file( \
                         data_file_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, file_order BIGINT, path VARCHAR, \
                         path_is_relative BOOLEAN, file_format VARCHAR, record_count BIGINT, \
                         file_size_bytes BIGINT, footer_size BIGINT, row_id_start BIGINT, \
                         partition_id BIGINT, encryption_key VARCHAR, mapping_id BIGINT, \
                         partial_max BIGINT \
                     )",
            )
            .await
            .unwrap();
        setup
            .query_sql(
                "CREATE TABLE ducklake_file_column_stats( \
                         data_file_id BIGINT, table_id BIGINT, column_id BIGINT, \
                         column_size_bytes BIGINT, value_count BIGINT, null_count BIGINT, \
                         min_value VARCHAR, max_value VARCHAR, contains_nan BOOLEAN, \
                         extra_stats VARCHAR \
                     )",
            )
            .await
            .unwrap();
        setup
            .query_sql(
                "CREATE TABLE ducklake_column( \
                         column_id BIGINT, begin_snapshot BIGINT, end_snapshot BIGINT, \
                         table_id BIGINT, column_order BIGINT, column_name VARCHAR, \
                         column_type VARCHAR, initial_default VARCHAR, default_value VARCHAR, \
                         nulls_allowed BOOLEAN, parent_column BIGINT, default_value_type VARCHAR, \
                         default_value_dialect VARCHAR \
                     )",
            )
            .await
            .unwrap();
        setup
            .query_sql(
                "CREATE TABLE ducklake_table_column_stats( \
                         table_id BIGINT, column_id BIGINT, contains_null BOOLEAN, \
                         contains_nan BOOLEAN, min_value VARCHAR, max_value VARCHAR, \
                         extra_stats VARCHAR \
                     )",
            )
            .await
            .unwrap();
        setup
            .query_sql(
                "CREATE TABLE ducklake_file_partition_value( \
                         data_file_id BIGINT, table_id BIGINT, partition_key_index BIGINT, \
                         partition_value VARCHAR \
                     )",
            )
            .await
            .unwrap();
        setup
            .query_sql(
                "CREATE TABLE ducklake_table_stats( \
                         table_id BIGINT, record_count BIGINT, next_row_id BIGINT, \
                         file_size_bytes BIGINT \
                     )",
            )
            .await
            .unwrap();
        setup
            .query_sql(
                "INSERT INTO ducklake_snapshot VALUES \
                     (1, TIMESTAMP '2024-01-01 00:00:00', 1, 1, 10)",
            )
            .await
            .unwrap();
        setup
            .query_sql("INSERT INTO ducklake_table_stats VALUES (1, 0, 0, 0)")
            .await
            .unwrap();
        setup
            .query_sql(
                "INSERT INTO ducklake_column VALUES \
                     (2, 1, NULL, 1, 1, 'value', 'int64', NULL, NULL, FALSE, NULL, NULL, NULL)",
            )
            .await
            .unwrap();

        let left = QuackCatalog::try_new(uri.clone(), token.clone()).unwrap();
        let right = QuackCatalog::try_new(uri.clone(), token.clone()).unwrap();
        let (left, right) = tokio::join!(
            left.commit_append_files(vec![quack_append_file(10, SnapshotId(1))]),
            right.commit_append_files(vec![quack_append_file(10, SnapshotId(1))])
        );

        let mut successes = 0;
        let mut conflicts = 0;
        for result in [left, right] {
            match result {
                Ok(snapshot) => {
                    assert_eq!(snapshot, SnapshotId(2));
                    successes += 1;
                }
                Err(StorageError::Conflict {
                    operation,
                    expected_snapshot,
                    found_snapshot,
                }) => {
                    assert_eq!(operation, "append");
                    assert_eq!(expected_snapshot, 1);
                    assert_eq!(found_snapshot, 2);
                    conflicts += 1;
                }
                Err(err) => panic!("expected conflict error, got {err:?}"),
            }
        }
        assert_eq!(successes, 1);
        assert_eq!(conflicts, 1);
        assert_eq!(
            setup
                .query_sql("SELECT COUNT(*) FROM ducklake_snapshot")
                .await
                .unwrap()
                .rows,
            vec![vec![DuckDbValue::Int64(2)]]
        );
        assert_eq!(
            setup
                .query_sql("SELECT COUNT(*) FROM ducklake_data_file")
                .await
                .unwrap()
                .rows,
            vec![vec![DuckDbValue::Int64(1)]]
        );

        for table in [
            "ducklake_table_stats",
            "ducklake_table_column_stats",
            "ducklake_file_partition_value",
            "ducklake_file_column_stats",
            "ducklake_delete_file",
            "ducklake_data_file",
            "ducklake_column",
            "ducklake_snapshot_changes",
            "ducklake_snapshot",
        ] {
            setup
                .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                .await
                .unwrap();
        }
    });
}

#[test]
fn live_quack_append_honors_begin_snapshot_override_when_enabled() {
    let live_enabled = std::env::var("DUCKLAKE_QUACK_LIVE_TEST")
        .ok()
        .is_some_and(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "on"));
    if !live_enabled {
        eprintln!(
            "skipping live Quack begin_snapshot override test; set DUCKLAKE_QUACK_LIVE_TEST=1 and run scripts/start-quack-server.sh"
        );
        return;
    }
    let _live_guard = QUACK_LIVE_TEST_LOCK.lock().unwrap();

    let uri =
        std::env::var("DUCKLAKE_QUACK_TEST_URI").unwrap_or_else(|_| "quack:localhost".to_string());
    let token = std::env::var("DUCKLAKE_QUACK_TEST_TOKEN").unwrap_or_else(|_| "asdf".to_string());
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
        let setup = QuackCatalog::try_new(uri.clone(), token.clone()).unwrap();
        for table in [
            "ducklake_table_stats",
            "ducklake_table_column_stats",
            "ducklake_file_partition_value",
            "ducklake_file_column_stats",
            "ducklake_delete_file",
            "ducklake_data_file",
            "ducklake_column",
            "ducklake_snapshot_changes",
            "ducklake_snapshot",
        ] {
            setup
                .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                .await
                .unwrap();
        }
        setup
            .query_sql(
                "CREATE TABLE ducklake_snapshot( \
                         snapshot_id BIGINT, snapshot_time TIMESTAMP, schema_version BIGINT, \
                         next_catalog_id BIGINT, next_file_id BIGINT \
                     )",
            )
            .await
            .unwrap();
        setup
            .query_sql(
                "CREATE TABLE ducklake_data_file( \
                         data_file_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, file_order BIGINT, path VARCHAR, \
                         path_is_relative BOOLEAN, file_format VARCHAR, record_count BIGINT, \
                         file_size_bytes BIGINT, footer_size BIGINT, row_id_start BIGINT, \
                         partition_id BIGINT, encryption_key VARCHAR, mapping_id BIGINT, \
                         partial_max BIGINT \
                     )",
            )
            .await
            .unwrap();
        setup
            .query_sql(
                "CREATE TABLE ducklake_file_column_stats( \
                         data_file_id BIGINT, table_id BIGINT, column_id BIGINT, \
                         column_size_bytes BIGINT, value_count BIGINT, null_count BIGINT, \
                         min_value VARCHAR, max_value VARCHAR, contains_nan BOOLEAN, \
                         extra_stats VARCHAR \
                     )",
            )
            .await
            .unwrap();
        setup
            .query_sql(
                "CREATE TABLE ducklake_file_partition_value( \
                         data_file_id BIGINT, table_id BIGINT, partition_key_index BIGINT, \
                         partition_value VARCHAR \
                     )",
            )
            .await
            .unwrap();
        setup
            .query_sql(
                "INSERT INTO ducklake_snapshot VALUES \
                     (3, TIMESTAMP '2024-01-01 00:00:00', 1, 1, 10)",
            )
            .await
            .unwrap();

        let append_snapshot = setup
            .commit_append_files(vec![
                AppendFile {
                    begin_snapshot: Some(SnapshotId(1)),
                    ..quack_append_file(10, SnapshotId(3))
                },
                quack_append_file(11, SnapshotId(3)),
            ])
            .await
            .unwrap();
        assert_eq!(append_snapshot, SnapshotId(4));
        assert_eq!(
            setup
                .query_sql(
                    "SELECT data_file_id, begin_snapshot \
                         FROM ducklake_data_file ORDER BY data_file_id"
                )
                .await
                .unwrap()
                .rows,
            vec![
                vec![DuckDbValue::Int64(10), DuckDbValue::Int64(1)],
                vec![DuckDbValue::Int64(11), DuckDbValue::Int64(4)],
            ]
        );

        for table in [
            "ducklake_file_partition_value",
            "ducklake_file_column_stats",
            "ducklake_data_file",
            "ducklake_snapshot",
        ] {
            setup
                .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                .await
                .unwrap();
        }
    });
}

#[test]
fn live_quack_merge_adjacent_compaction_when_enabled() {
    let live_enabled = std::env::var("DUCKLAKE_QUACK_LIVE_TEST")
        .ok()
        .is_some_and(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "on"));
    if !live_enabled {
        eprintln!(
            "skipping live Quack compaction test; set DUCKLAKE_QUACK_LIVE_TEST=1 and run scripts/start-quack-server.sh"
        );
        return;
    }
    let _live_guard = QUACK_LIVE_TEST_LOCK.lock().unwrap();

    let uri =
        std::env::var("DUCKLAKE_QUACK_TEST_URI").unwrap_or_else(|_| "quack:localhost".to_string());
    let token = std::env::var("DUCKLAKE_QUACK_TEST_TOKEN").unwrap_or_else(|_| "asdf".to_string());
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
            let catalog = QuackCatalog::try_new(uri, token).unwrap();
            for table in [
                "ducklake_metadata",
                "ducklake_schema",
                "ducklake_table",
                "ducklake_inlined_data_1_1",
                "ducklake_inlined_delete_1",
                "ducklake_table_stats",
                "ducklake_file_partition_value",
                "ducklake_file_variant_stats",
                "ducklake_file_column_stats",
                "ducklake_delete_file",
                "ducklake_files_scheduled_for_deletion",
                "ducklake_data_file",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }

            catalog
                .query_sql(
                    "CREATE TABLE ducklake_metadata( \
                         key VARCHAR, value VARCHAR, scope VARCHAR, scope_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_schema( \
                         schema_id BIGINT, schema_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_name VARCHAR, path VARCHAR, \
                         path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_table( \
                         table_id BIGINT, table_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_id BIGINT, table_name VARCHAR, \
                         path VARCHAR, path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_snapshot( \
                         snapshot_id BIGINT, snapshot_time TIMESTAMP, schema_version BIGINT, \
                         next_catalog_id BIGINT, next_file_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_snapshot_changes( \
                         snapshot_id BIGINT, changes_made VARCHAR, author VARCHAR, \
                         commit_message VARCHAR, commit_extra_info VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_data_file( \
                         data_file_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, file_order BIGINT, path VARCHAR, \
                         path_is_relative BOOLEAN, file_format VARCHAR, record_count BIGINT, \
                         file_size_bytes BIGINT, footer_size BIGINT, row_id_start BIGINT, \
                         partition_id BIGINT, encryption_key VARCHAR, mapping_id BIGINT, \
                         partial_max BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_delete_file( \
                         delete_file_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, data_file_id BIGINT, path VARCHAR, \
                         path_is_relative BOOLEAN, format VARCHAR, delete_count BIGINT, \
                         file_size_bytes BIGINT, footer_size BIGINT, encryption_key VARCHAR, \
                         partial_max BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_file_column_stats( \
                         data_file_id BIGINT, table_id BIGINT, column_id BIGINT, \
                         column_size_bytes BIGINT, value_count BIGINT, null_count BIGINT, \
                         min_value VARCHAR, max_value VARCHAR, contains_nan BOOLEAN, \
                         extra_stats VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_file_variant_stats( \
                         data_file_id BIGINT, table_id BIGINT, column_id BIGINT, \
                         variant_path VARCHAR, shredded_type VARCHAR, column_size_bytes BIGINT, \
                         value_count BIGINT, null_count BIGINT, min_value VARCHAR, \
                         max_value VARCHAR, contains_nan BOOLEAN, extra_stats VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_file_partition_value( \
                         data_file_id BIGINT, table_id BIGINT, partition_key_index BIGINT, \
                         partition_value VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_files_scheduled_for_deletion( \
                         data_file_id BIGINT, path VARCHAR, path_is_relative BOOLEAN, \
                         schedule_start TIMESTAMP \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_table_stats( \
                         table_id BIGINT, record_count BIGINT, next_row_id BIGINT, \
                         file_size_bytes BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_inlined_delete_1( \
                         file_id BIGINT, row_id BIGINT, begin_snapshot BIGINT \
                     )",
                )
                .await
                .unwrap();

            catalog
                .query_sql(
                    "INSERT INTO ducklake_metadata VALUES \
                     ('version', '1.0', NULL, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_schema VALUES \
                     (1, '00000000-0000-0000-0000-000000000001', 1, NULL, 'main', NULL, FALSE)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_table VALUES \
                     (1, '00000000-0000-0000-0000-000000000002', 1, NULL, 1, 'merge_src', NULL, FALSE)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_snapshot VALUES \
                     (1, TIMESTAMP '2024-01-01 00:00:00', 1, 10, 20)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_data_file VALUES \
                     (10, 1, 1, NULL, 0, 'source-a.parquet', TRUE, 'parquet', 1, 30, NULL, NULL, NULL, NULL, NULL, NULL), \
                     (11, 1, 1, NULL, 1, 'source-b.parquet', TRUE, 'parquet', 2, 40, NULL, NULL, NULL, NULL, NULL, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_delete_file VALUES \
                     (12, 1, 1, NULL, 10, 'delete-source-a.parquet', TRUE, 'parquet', 1, 5, 1, NULL, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_file_column_stats VALUES \
                     (10, 1, 2, 4, 1, 0, '1', '1', FALSE, NULL), \
                     (11, 1, 2, 8, 2, 0, '2', '3', FALSE, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_file_variant_stats VALUES \
                     (10, 1, 2, '$.a', 'int64', 4, 1, 0, '1', '1', FALSE, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_file_partition_value VALUES \
                     (10, 1, 0, 'old-a'), \
                     (11, 1, 0, 'old-b')",
                )
                .await
                .unwrap();
            catalog
                .query_sql("INSERT INTO ducklake_table_stats VALUES (1, 3, 3, 100)")
                .await
                .unwrap();
            catalog
                .query_sql("INSERT INTO ducklake_inlined_delete_1 VALUES (10, 0, 1), (11, 0, 1)")
                .await
                .unwrap();

            let mut compacted_file = quack_append_file(20, SnapshotId(1));
            compacted_file.path = "compact-20.parquet".into();
            compacted_file.record_count = 3;
            compacted_file.file_size_bytes = 55;
            compacted_file.partition_id = Some(PartitionId(7));
            compacted_file.partition_values = vec![FilePartitionValue {
                data_file_id: DataFileId(20),
                table_id: TableId(1),
                partition_key_index: 0,
                partition_value: Some("compact".into()),
            }];
            compacted_file.column_stats = vec![FileColumnStats {
                data_file_id: DataFileId(20),
                table_id: TableId(1),
                column_id: ColumnId(2),
                column_size_bytes: Some(12),
                value_count: Some(3),
                null_count: Some(0),
                min_value: Some("1".into()),
                max_value: Some("3".into()),
                contains_nan: Some(false),
                extra_stats: None,
            }];
            let new_snapshot = catalog
                .commit_merge_adjacent_compaction(MergeAdjacentCompactionCommit {
                    table_id: TableId(1),
                    expected_snapshot: SnapshotId(1),
                    source_data_file_ids: vec![DataFileId(10), DataFileId(11)],
                    compacted_files: vec![compacted_file],
                })
                .await
                .unwrap();
            assert_eq!(new_snapshot, SnapshotId(2));

            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT data_file_id, begin_snapshot, file_order, path, partition_id \
                         FROM ducklake_data_file ORDER BY data_file_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(20),
                    DuckDbValue::Int64(2),
                    DuckDbValue::Int64(20),
                    DuckDbValue::Utf8("compact-20.parquet".into()),
                    DuckDbValue::Int64(7),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT data_file_id, path, path_is_relative \
                         FROM ducklake_files_scheduled_for_deletion ORDER BY data_file_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![
                        DuckDbValue::Int64(10),
                        DuckDbValue::Utf8("source-a.parquet".into()),
                        DuckDbValue::Boolean(true),
                    ],
                    vec![
                        DuckDbValue::Int64(11),
                        DuckDbValue::Utf8("source-b.parquet".into()),
                        DuckDbValue::Boolean(true),
                    ],
                    vec![
                        DuckDbValue::Int64(12),
                        DuckDbValue::Utf8("delete-source-a.parquet".into()),
                        DuckDbValue::Boolean(true),
                    ],
                ]
            );
            assert_eq!(
                catalog
                    .query_sql("SELECT COUNT(*) FROM ducklake_delete_file")
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(0)]]
            );
            assert_eq!(
                catalog
                    .query_sql("SELECT COUNT(*) FROM ducklake_file_variant_stats")
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(0)]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT data_file_id, column_id, min_value, max_value \
                         FROM ducklake_file_column_stats ORDER BY data_file_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(20),
                    DuckDbValue::Int64(2),
                    DuckDbValue::Utf8("1".into()),
                    DuckDbValue::Utf8("3".into()),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT data_file_id, partition_key_index, partition_value \
                         FROM ducklake_file_partition_value ORDER BY data_file_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(20),
                    DuckDbValue::Int64(0),
                    DuckDbValue::Utf8("compact".into()),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql("SELECT COUNT(*) FROM ducklake_inlined_delete_1")
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(0)]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT snapshot_id, next_file_id \
                         FROM ducklake_snapshot ORDER BY snapshot_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![DuckDbValue::Int64(1), DuckDbValue::Int64(20)],
                    vec![DuckDbValue::Int64(2), DuckDbValue::Int64(21)],
                ]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT changes_made, commit_message \
                         FROM ducklake_snapshot_changes WHERE snapshot_id = 2"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Utf8("merge_adjacent:1".into()),
                    DuckDbValue::Utf8("datafusion merge adjacent".into()),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT record_count, next_row_id, file_size_bytes \
                         FROM ducklake_table_stats WHERE table_id = 1"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(3),
                    DuckDbValue::Int64(3),
                    DuckDbValue::Int64(85),
                ]]
            );

            for table in [
                "ducklake_inlined_delete_1",
                "ducklake_table_stats",
                "ducklake_file_partition_value",
                "ducklake_file_variant_stats",
                "ducklake_file_column_stats",
                "ducklake_delete_file",
                "ducklake_files_scheduled_for_deletion",
                "ducklake_data_file",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }
            catalog.disconnect().await.unwrap();
        });
}

#[test]
fn live_quack_rewrite_data_files_when_enabled() {
    let live_enabled = std::env::var("DUCKLAKE_QUACK_LIVE_TEST")
        .ok()
        .is_some_and(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "on"));
    if !live_enabled {
        eprintln!(
            "skipping live Quack rewrite-data-files test; set DUCKLAKE_QUACK_LIVE_TEST=1 and run scripts/start-quack-server.sh"
        );
        return;
    }
    let _live_guard = QUACK_LIVE_TEST_LOCK.lock().unwrap();

    let uri =
        std::env::var("DUCKLAKE_QUACK_TEST_URI").unwrap_or_else(|_| "quack:localhost".to_string());
    let token = std::env::var("DUCKLAKE_QUACK_TEST_TOKEN").unwrap_or_else(|_| "asdf".to_string());
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
            let catalog = QuackCatalog::try_new(uri, token).unwrap();
            for table in [
                "ducklake_table_stats",
                "ducklake_file_column_stats",
                "ducklake_delete_file",
                "ducklake_files_scheduled_for_deletion",
                "ducklake_data_file",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }

            catalog
                .query_sql(
                    "CREATE TABLE ducklake_snapshot( \
                         snapshot_id BIGINT, snapshot_time TIMESTAMP, schema_version BIGINT, \
                         next_catalog_id BIGINT, next_file_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_snapshot_changes( \
                         snapshot_id BIGINT, changes_made VARCHAR, author VARCHAR, \
                         commit_message VARCHAR, commit_extra_info VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_data_file( \
                         data_file_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, file_order BIGINT, path VARCHAR, \
                         path_is_relative BOOLEAN, file_format VARCHAR, record_count BIGINT, \
                         file_size_bytes BIGINT, footer_size BIGINT, row_id_start BIGINT, \
                         partition_id BIGINT, encryption_key VARCHAR, mapping_id BIGINT, \
                         partial_max BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_delete_file( \
                         delete_file_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, data_file_id BIGINT, path VARCHAR, \
                         path_is_relative BOOLEAN, format VARCHAR, delete_count BIGINT, \
                         file_size_bytes BIGINT, footer_size BIGINT, encryption_key VARCHAR, \
                         partial_max BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_file_column_stats( \
                         data_file_id BIGINT, table_id BIGINT, column_id BIGINT, \
                         column_size_bytes BIGINT, value_count BIGINT, null_count BIGINT, \
                         min_value VARCHAR, max_value VARCHAR, contains_nan BOOLEAN, \
                         extra_stats VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_files_scheduled_for_deletion( \
                         data_file_id BIGINT, path VARCHAR, path_is_relative BOOLEAN, \
                         schedule_start TIMESTAMP \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_table_stats( \
                         table_id BIGINT, record_count BIGINT, next_row_id BIGINT, \
                         file_size_bytes BIGINT \
                     )",
                )
                .await
                .unwrap();

            catalog
                .query_sql(
                    "INSERT INTO ducklake_snapshot VALUES \
                     (1, TIMESTAMP '2024-01-01 00:00:00', 1, 10, 20)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_data_file VALUES \
                     (10, 1, 1, NULL, 0, 'rewrite-source-a.parquet', TRUE, 'parquet', 1, 10, NULL, NULL, NULL, NULL, NULL, NULL), \
                     (11, 1, 1, NULL, 1, 'rewrite-source-b.parquet', TRUE, 'parquet', 2, 20, NULL, NULL, NULL, NULL, NULL, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_delete_file VALUES \
                     (12, 1, 1, NULL, 10, 'rewrite-delete-a.parquet', TRUE, 'parquet', 1, 5, 1, NULL, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_file_column_stats VALUES \
                     (10, 1, 1, 4, 1, 0, '1', '1', FALSE, NULL), \
                     (11, 1, 1, 8, 2, 0, '2', '3', FALSE, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql("INSERT INTO ducklake_table_stats VALUES (1, 3, 3, 100)")
                .await
                .unwrap();

            let rewritten_file = AppendFile {
                table_id: TableId(1),
                expected_snapshot: SnapshotId(1),
                data_file_id: DataFileId(20),
                begin_snapshot: None,
                path: "rewrite-20.parquet".into(),
                path_is_relative: true,
                record_count: 3,
                file_size_bytes: 25,
                footer_size: None,
                row_id_start: None,
                explicit_row_ids: true,
                column_stats: vec![FileColumnStats {
                    data_file_id: DataFileId(20),
                    table_id: TableId(1),
                    column_id: ColumnId(1),
                    column_size_bytes: Some(12),
                    value_count: Some(3),
                    null_count: Some(0),
                    min_value: Some("1".into()),
                    max_value: Some("3".into()),
                    contains_nan: Some(false),
                    extra_stats: None,
                }],
                variant_stats: Vec::new(),
                partition_id: None,
                partition_values: Vec::new(),
                encryption_key: None,
                mapping_id: None,
                partial_max: None,
            };
            let new_snapshot = catalog
                .commit_rewrite_data_files(RewriteDataFilesCommit {
                    table_id: TableId(1),
                    expected_snapshot: SnapshotId(1),
                    source_data_file_ids: vec![DataFileId(10), DataFileId(11)],
                    rewritten_files: vec![rewritten_file],
                })
                .await
                .unwrap();
            assert_eq!(new_snapshot, SnapshotId(2));

            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT data_file_id, begin_snapshot, end_snapshot, path \
                         FROM ducklake_data_file ORDER BY data_file_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![
                        DuckDbValue::Int64(10),
                        DuckDbValue::Int64(1),
                        DuckDbValue::Int64(2),
                        DuckDbValue::Utf8("rewrite-source-a.parquet".into()),
                    ],
                    vec![
                        DuckDbValue::Int64(11),
                        DuckDbValue::Int64(1),
                        DuckDbValue::Int64(2),
                        DuckDbValue::Utf8("rewrite-source-b.parquet".into()),
                    ],
                    vec![
                        DuckDbValue::Int64(20),
                        DuckDbValue::Int64(2),
                        DuckDbValue::Null,
                        DuckDbValue::Utf8("rewrite-20.parquet".into()),
                    ],
                ]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT end_snapshot FROM ducklake_delete_file WHERE delete_file_id = 12"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(2)]]
            );
            assert_eq!(
                catalog
                    .query_sql("SELECT COUNT(*) FROM ducklake_files_scheduled_for_deletion")
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(0)]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT snapshot_id, next_file_id \
                         FROM ducklake_snapshot ORDER BY snapshot_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![DuckDbValue::Int64(1), DuckDbValue::Int64(20)],
                    vec![DuckDbValue::Int64(2), DuckDbValue::Int64(21)],
                ]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT changes_made, commit_message \
                         FROM ducklake_snapshot_changes WHERE snapshot_id = 2"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Utf8("rewrite_data_files:1".into()),
                    DuckDbValue::Utf8("datafusion rewrite data files".into()),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT record_count, next_row_id, file_size_bytes \
                         FROM ducklake_table_stats WHERE table_id = 1"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(3),
                    DuckDbValue::Int64(3),
                    DuckDbValue::Int64(95),
                ]]
            );

            for table in [
                "ducklake_table_stats",
                "ducklake_file_column_stats",
                "ducklake_delete_file",
                "ducklake_files_scheduled_for_deletion",
                "ducklake_data_file",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }
            catalog.disconnect().await.unwrap();
        });
}

#[test]
fn live_quack_materialized_overwrite_schedules_delete_cleanup_with_table_path_when_enabled() {
    let live_enabled = std::env::var("DUCKLAKE_QUACK_LIVE_TEST")
        .ok()
        .is_some_and(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "on"));
    if !live_enabled {
        eprintln!(
            "skipping live Quack overwrite cleanup test; set DUCKLAKE_QUACK_LIVE_TEST=1 and run scripts/start-quack-server.sh"
        );
        return;
    }
    let _live_guard = QUACK_LIVE_TEST_LOCK.lock().unwrap();

    let uri =
        std::env::var("DUCKLAKE_QUACK_TEST_URI").unwrap_or_else(|_| "quack:localhost".to_string());
    let token = std::env::var("DUCKLAKE_QUACK_TEST_TOKEN").unwrap_or_else(|_| "asdf".to_string());
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
            let catalog = QuackCatalog::try_new(uri, token).unwrap();
            for table in [
                "ducklake_metadata",
                "ducklake_schema",
                "ducklake_table",
                "ducklake_table_stats",
                "ducklake_delete_file",
                "ducklake_files_scheduled_for_deletion",
                "ducklake_data_file",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }

            catalog
                .query_sql(
                    "CREATE TABLE ducklake_metadata( \
                         key VARCHAR, value VARCHAR, scope VARCHAR, scope_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_schema( \
                         schema_id BIGINT, schema_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_name VARCHAR, path VARCHAR, \
                         path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_table( \
                         table_id BIGINT, table_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_id BIGINT, table_name VARCHAR, \
                         path VARCHAR, path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_snapshot( \
                         snapshot_id BIGINT, snapshot_time TIMESTAMP, schema_version BIGINT, \
                         next_catalog_id BIGINT, next_file_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_snapshot_changes( \
                         snapshot_id BIGINT, changes_made VARCHAR, author VARCHAR, \
                         commit_message VARCHAR, commit_extra_info VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_data_file( \
                         data_file_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, file_order BIGINT, path VARCHAR, \
                         path_is_relative BOOLEAN, file_format VARCHAR, record_count BIGINT, \
                         file_size_bytes BIGINT, footer_size BIGINT, row_id_start BIGINT, \
                         partition_id BIGINT, encryption_key VARCHAR, mapping_id BIGINT, \
                         partial_max BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_delete_file( \
                         delete_file_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, data_file_id BIGINT, path VARCHAR, \
                         path_is_relative BOOLEAN, format VARCHAR, delete_count BIGINT, \
                         file_size_bytes BIGINT, footer_size BIGINT, encryption_key VARCHAR, \
                         partial_max BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_files_scheduled_for_deletion( \
                         data_file_id BIGINT, path VARCHAR, path_is_relative BOOLEAN, \
                         schedule_start TIMESTAMP \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TABLE ducklake_table_stats( \
                         table_id BIGINT, record_count BIGINT, next_row_id BIGINT, \
                         file_size_bytes BIGINT \
                     )",
                )
                .await
                .unwrap();

            catalog
                .query_sql(
                    "INSERT INTO ducklake_metadata VALUES \
                     ('version', '1.0', NULL, NULL), \
                     ('data_path', '/tmp/ducklake-data', NULL, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_schema VALUES \
                     (1, '00000000-0000-0000-0000-000000000001', 1, NULL, 'main', 'main', TRUE)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_table VALUES \
                     (1, '00000000-0000-0000-0000-000000000002', 1, NULL, 1, 'numbers', 'numbers', TRUE)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_snapshot VALUES \
                     (1, TIMESTAMP '2024-01-01 00:00:00', 1, 10, 11)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_data_file VALUES \
                     (1, 1, 1, NULL, 0, 'data.parquet', TRUE, 'parquet', 4, 40, NULL, 0, NULL, NULL, NULL, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_delete_file VALUES \
                     (10, 1, 1, NULL, 1, 'old-delete.parquet', TRUE, 'parquet', 1, 12, NULL, NULL, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql("INSERT INTO ducklake_table_stats VALUES (1, 3, 4, 40)")
                .await
                .unwrap();

            let snapshot = catalog
                .commit_table_changes(TableChangeCommit {
                    table_id: TableId(1),
                    expected_snapshot: SnapshotId(1),
                    append_files: Vec::new(),
                    delete_files: vec![DeleteFileCommit {
                        table_id: TableId(1),
                        expected_snapshot: SnapshotId(1),
                        delete_file_id: DeleteFileId(11),
                        begin_snapshot: Some(SnapshotId(1)),
                        data_file_id: DataFileId(1),
                        path: "replacement-delete.parquet".into(),
                        path_is_relative: true,
                        format: DeleteFormat::Parquet,
                        delete_count: 1,
                        rows_deleted: 0,
                        file_size_bytes: 12,
                        footer_size: None,
                        partial_max: Some(SnapshotId(1)),
                        overwritten_delete_file_id: Some(DeleteFileId(10)),
                    }],
                    inlined_file_deletions: Vec::new(),
                    inlined_data_appends: Vec::new(),
                    inlined_data_deletions: Vec::new(),
                    flushed_inlined_data: Vec::new(),
                    flushed_inlined_file_deletions: Vec::new(),
                    changes_made: "flushed_inlined_data:1".into(),
                    commit_message: "datafusion flush inlined deletes".into(),
                })
                .await
                .unwrap();
            assert_eq!(snapshot, SnapshotId(2));
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT data_file_id, path, path_is_relative \
                         FROM ducklake_files_scheduled_for_deletion"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(10),
                    DuckDbValue::Utf8("main/numbers/old-delete.parquet".into()),
                    DuckDbValue::Boolean(true),
                ]]
            );

            for table in [
                "ducklake_metadata",
                "ducklake_schema",
                "ducklake_table",
                "ducklake_table_stats",
                "ducklake_delete_file",
                "ducklake_files_scheduled_for_deletion",
                "ducklake_data_file",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }
            catalog.disconnect().await.unwrap();
        });
}

#[test]
fn live_quack_create_drop_table_commits_ducklake_metadata_when_enabled() {
    let live_enabled = std::env::var("DUCKLAKE_QUACK_LIVE_TEST")
        .ok()
        .is_some_and(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "on"));
    if !live_enabled {
        eprintln!(
            "skipping live Quack table DDL test; set DUCKLAKE_QUACK_LIVE_TEST=1 and run scripts/start-quack-server.sh"
        );
        return;
    }
    let _live_guard = QUACK_LIVE_TEST_LOCK.lock().unwrap();

    let uri =
        std::env::var("DUCKLAKE_QUACK_TEST_URI").unwrap_or_else(|_| "quack:localhost".to_string());
    let token = std::env::var("DUCKLAKE_QUACK_TEST_TOKEN").unwrap_or_else(|_| "asdf".to_string());
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
            let catalog = QuackCatalog::try_new(uri, token).unwrap();
            for table in [
                "ducklake_inlined_data_10_2",
                "ducklake_inlined_data_tables",
                "ducklake_delete_file",
                "ducklake_data_file",
                "ducklake_column",
                "ducklake_table",
                "ducklake_schema",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }

            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot( \
                         snapshot_id BIGINT, snapshot_time TIMESTAMP, schema_version BIGINT, \
                         next_catalog_id BIGINT, next_file_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot_changes( \
                         snapshot_id BIGINT, changes_made VARCHAR, author VARCHAR, \
                         commit_message VARCHAR, commit_extra_info VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_schema( \
                         schema_id BIGINT, schema_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_name VARCHAR, path VARCHAR, \
                         path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_table( \
                         table_id BIGINT, table_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_id BIGINT, table_name VARCHAR, \
                         path VARCHAR, path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_column( \
                         column_id BIGINT, begin_snapshot BIGINT, end_snapshot BIGINT, \
                         table_id BIGINT, column_order BIGINT, column_name VARCHAR, \
                         column_type VARCHAR, initial_default VARCHAR, default_value VARCHAR, \
                         nulls_allowed BOOLEAN, parent_column BIGINT, default_value_type VARCHAR, \
                         default_value_dialect VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_data_file( \
                         data_file_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, file_order BIGINT, path VARCHAR, \
                         path_is_relative BOOLEAN, file_format VARCHAR, record_count BIGINT, \
                         file_size_bytes BIGINT, footer_size BIGINT, row_id_start BIGINT, \
                         partition_id BIGINT, encryption_key VARCHAR, mapping_id BIGINT, \
                         partial_max BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_delete_file( \
                         delete_file_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, data_file_id BIGINT, path VARCHAR, \
                         path_is_relative BOOLEAN, format VARCHAR, delete_count BIGINT, \
                         file_size_bytes BIGINT, footer_size BIGINT, encryption_key VARCHAR, \
                         partial_max BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_inlined_data_tables( \
                         table_id BIGINT, table_name VARCHAR, schema_version BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_snapshot VALUES \
                     (1, TIMESTAMP '2026-06-26 00:00:00', 1, 10, 20)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_schema VALUES \
                     (1, '550e8400-e29b-41d4-a716-446655440001'::UUID, 1, NULL, 'main', 'main/', TRUE)",
                )
                .await
                .unwrap();

            let created = catalog
                .commit_create_table(CreateTableCommit {
                    schema_name: "main".into(),
                    table_name: "created".into(),
                    columns: vec![
                        CreateTableColumn {
                            column_id: ColumnId(1),
                            column_order: 1,
                            column_name: "id".into(),
                            column_type: "int32".into(),
                            nulls_allowed: false,
                            parent_column: None,
                        },
                        CreateTableColumn {
                            column_id: ColumnId(2),
                            column_order: 2,
                            column_name: "name".into(),
                            column_type: "varchar".into(),
                            nulls_allowed: true,
                            parent_column: None,
                        },
                    ],
                    commit_message: "quack create table".into(),
                })
                .await
                .unwrap();
            assert_eq!(created, SnapshotId(2));
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT table_id, begin_snapshot, end_snapshot, schema_id, table_name, path, path_is_relative \
                         FROM ducklake_table WHERE table_name = 'created'"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(10),
                    DuckDbValue::Int64(2),
                    DuckDbValue::Null,
                    DuckDbValue::Int64(1),
                    DuckDbValue::Utf8("created".into()),
                    DuckDbValue::Utf8("created/".into()),
                    DuckDbValue::Boolean(true),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT column_id, column_order, column_name, column_type, nulls_allowed \
                         FROM ducklake_column WHERE table_id = 10 ORDER BY column_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![
                        DuckDbValue::Int64(1),
                        DuckDbValue::Int64(1),
                        DuckDbValue::Utf8("id".into()),
                        DuckDbValue::Utf8("int32".into()),
                        DuckDbValue::Boolean(false),
                    ],
                    vec![
                        DuckDbValue::Int64(2),
                        DuckDbValue::Int64(2),
                        DuckDbValue::Utf8("name".into()),
                        DuckDbValue::Utf8("varchar".into()),
                        DuckDbValue::Boolean(true),
                    ],
                ]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT snapshot_id, schema_version, next_catalog_id, next_file_id \
                         FROM ducklake_snapshot ORDER BY snapshot_id DESC LIMIT 1"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(2),
                    DuckDbValue::Int64(2),
                    DuckDbValue::Int64(11),
                    DuckDbValue::Int64(20),
                ]]
            );

            catalog
                .query_sql(
                    "INSERT INTO ducklake_data_file VALUES \
                     (20, 10, 2, NULL, 20, 'created.parquet', TRUE, 'parquet', 1, 10, NULL, 0, NULL, NULL, NULL, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_delete_file VALUES \
                     (21, 10, 2, NULL, 20, 'created-delete.parquet', TRUE, 'deletion-vector', 1, 10, 0, NULL, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_inlined_data_10_2( \
                         begin_snapshot BIGINT, end_snapshot BIGINT, row_id BIGINT, \
                         id INTEGER, name VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql("INSERT INTO ducklake_inlined_data_10_2 VALUES (2, NULL, 0, 7, 'seven')")
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_inlined_data_tables VALUES \
                     (10, 'ducklake_inlined_data_10_2', 2)",
                )
                .await
                .unwrap();

            let dropped = catalog
                .commit_drop_table(DropTableCommit {
                    schema_name: "main".into(),
                    table_name: "created".into(),
                    commit_message: "quack drop table".into(),
                })
                .await
                .unwrap();
            assert_eq!(dropped, SnapshotId(3));
            assert_eq!(
                catalog
                    .query_sql("SELECT end_snapshot FROM ducklake_table WHERE table_id = 10")
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(3)]]
            );
            for table in [
                "ducklake_column",
                "ducklake_data_file",
                "ducklake_delete_file",
            ] {
                assert_eq!(
                    catalog
                        .query_sql(format!(
                            "SELECT COUNT(*) FROM {table} WHERE table_id = 10 AND end_snapshot IS NULL"
                        ))
                        .await
                        .unwrap()
                        .rows,
                    vec![vec![DuckDbValue::Int64(0)]],
                    "{table}"
                );
            }
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT COUNT(*) FROM ducklake_inlined_data_10_2 WHERE end_snapshot IS NULL"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(0)]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT changes_made FROM ducklake_snapshot_changes ORDER BY snapshot_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![DuckDbValue::Utf8("created_table:\"main\".\"created\"".into())],
                    vec![DuckDbValue::Utf8("dropped_table:10".into())],
                ]
            );

            for table in [
                "ducklake_inlined_data_10_2",
                "ducklake_inlined_data_tables",
                "ducklake_delete_file",
                "ducklake_data_file",
                "ducklake_column",
                "ducklake_table",
                "ducklake_schema",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }
            catalog.disconnect().await.unwrap();
        });
}

#[test]
fn live_quack_create_drop_schema_commits_ducklake_metadata_when_enabled() {
    let live_enabled = std::env::var("DUCKLAKE_QUACK_LIVE_TEST")
        .ok()
        .is_some_and(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "on"));
    if !live_enabled {
        eprintln!(
            "skipping live Quack schema DDL test; set DUCKLAKE_QUACK_LIVE_TEST=1 and run scripts/start-quack-server.sh"
        );
        return;
    }
    let _live_guard = QUACK_LIVE_TEST_LOCK.lock().unwrap();

    let uri =
        std::env::var("DUCKLAKE_QUACK_TEST_URI").unwrap_or_else(|_| "quack:localhost".to_string());
    let token = std::env::var("DUCKLAKE_QUACK_TEST_TOKEN").unwrap_or_else(|_| "asdf".to_string());
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
            let catalog = QuackCatalog::try_new(uri, token).unwrap();
            for table in [
                "ducklake_inlined_data_11_3",
                "ducklake_inlined_data_tables",
                "ducklake_delete_file",
                "ducklake_data_file",
                "ducklake_column",
                "ducklake_view",
                "ducklake_table",
                "ducklake_schema",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }

            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot( \
                         snapshot_id BIGINT, snapshot_time TIMESTAMP, schema_version BIGINT, \
                         next_catalog_id BIGINT, next_file_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot_changes( \
                         snapshot_id BIGINT, changes_made VARCHAR, author VARCHAR, \
                         commit_message VARCHAR, commit_extra_info VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_schema( \
                         schema_id BIGINT, schema_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_name VARCHAR, path VARCHAR, \
                         path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_table( \
                         table_id BIGINT, table_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_id BIGINT, table_name VARCHAR, \
                         path VARCHAR, path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_view( \
                         view_id BIGINT, view_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_id BIGINT, view_name VARCHAR, \
                         dialect VARCHAR, sql VARCHAR, column_aliases VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_column( \
                         column_id BIGINT, begin_snapshot BIGINT, end_snapshot BIGINT, \
                         table_id BIGINT, column_order BIGINT, column_name VARCHAR, \
                         column_type VARCHAR, initial_default VARCHAR, default_value VARCHAR, \
                         nulls_allowed BOOLEAN, parent_column BIGINT, default_value_type VARCHAR, \
                         default_value_dialect VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_data_file( \
                         data_file_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, file_order BIGINT, path VARCHAR, \
                         path_is_relative BOOLEAN, file_format VARCHAR, record_count BIGINT, \
                         file_size_bytes BIGINT, footer_size BIGINT, row_id_start BIGINT, \
                         partition_id BIGINT, encryption_key VARCHAR, mapping_id BIGINT, \
                         partial_max BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_delete_file( \
                         delete_file_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, data_file_id BIGINT, path VARCHAR, \
                         path_is_relative BOOLEAN, format VARCHAR, delete_count BIGINT, \
                         file_size_bytes BIGINT, footer_size BIGINT, encryption_key VARCHAR, \
                         partial_max BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_inlined_data_tables( \
                         table_id BIGINT, table_name VARCHAR, schema_version BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_snapshot VALUES \
                     (1, TIMESTAMP '2026-06-26 00:00:00', 1, 10, 20)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_schema VALUES \
                     (1, '550e8400-e29b-41d4-a716-446655440001'::UUID, 1, NULL, 'main', 'main/', TRUE)",
                )
                .await
                .unwrap();

            let created_schema = catalog
                .commit_create_schema(CreateSchemaCommit {
                    schema_name: "analytics".into(),
                    commit_message: "quack create schema".into(),
                })
                .await
                .unwrap();
            assert_eq!(created_schema, SnapshotId(2));
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT schema_id, begin_snapshot, end_snapshot, schema_name, path, path_is_relative \
                         FROM ducklake_schema WHERE schema_name = 'analytics'"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(10),
                    DuckDbValue::Int64(2),
                    DuckDbValue::Null,
                    DuckDbValue::Utf8("analytics".into()),
                    DuckDbValue::Utf8("analytics/".into()),
                    DuckDbValue::Boolean(true),
                ]]
            );

            let created_table = catalog
                .commit_create_table(CreateTableCommit {
                    schema_name: "analytics".into(),
                    table_name: "events".into(),
                    columns: vec![
                        CreateTableColumn {
                            column_id: ColumnId(1),
                            column_order: 1,
                            column_name: "id".into(),
                            column_type: "int32".into(),
                            nulls_allowed: false,
                            parent_column: None,
                        },
                        CreateTableColumn {
                            column_id: ColumnId(2),
                            column_order: 2,
                            column_name: "name".into(),
                            column_type: "varchar".into(),
                            nulls_allowed: true,
                            parent_column: None,
                        },
                    ],
                    commit_message: "quack create schema table".into(),
                })
                .await
                .unwrap();
            assert_eq!(created_table, SnapshotId(3));

            catalog
                .query_sql(
                    "INSERT INTO ducklake_data_file VALUES \
                     (20, 11, 3, NULL, 20, 'analytics/events.parquet', TRUE, 'parquet', 1, 10, NULL, 0, NULL, NULL, NULL, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_delete_file VALUES \
                     (21, 11, 3, NULL, 20, 'analytics/events-delete.parquet', TRUE, 'parquet', 1, 10, 0, NULL, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_inlined_data_11_3( \
                         begin_snapshot BIGINT, end_snapshot BIGINT, row_id BIGINT, \
                         id INTEGER, name VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql("INSERT INTO ducklake_inlined_data_11_3 VALUES (3, NULL, 0, 7, 'seven')")
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_inlined_data_tables VALUES \
                     (11, 'ducklake_inlined_data_11_3', 3)",
                )
                .await
                .unwrap();

            let created_view = catalog
                .commit_create_view(CreateViewCommit {
                    schema_name: "analytics".into(),
                    view_name: "event_names".into(),
                    dialect: "duckdb".into(),
                    sql: "SELECT name FROM {DUCKLAKE_CATALOG}.analytics.events".into(),
                    column_aliases: Some(String::new()),
                    commit_message: "quack create schema view".into(),
                })
                .await
                .unwrap();
            assert_eq!(created_view, SnapshotId(4));

            let restricted = catalog
                .commit_drop_schema(DropSchemaCommit {
                    schema_name: "analytics".into(),
                    cascade: false,
                    commit_message: "quack restricted drop schema".into(),
                })
                .await
                .unwrap_err();
            assert!(format!("{restricted}").contains("DuckLake schema analytics is not empty"));

            let dropped = catalog
                .commit_drop_schema(DropSchemaCommit {
                    schema_name: "analytics".into(),
                    cascade: true,
                    commit_message: "quack cascade drop schema".into(),
                })
                .await
                .unwrap();
            assert_eq!(dropped, SnapshotId(5));

            assert_eq!(
                catalog
                    .query_sql("SELECT end_snapshot FROM ducklake_schema WHERE schema_id = 10")
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(5)]]
            );
            assert_eq!(
                catalog
                    .query_sql("SELECT end_snapshot FROM ducklake_table WHERE table_id = 11")
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(5)]]
            );
            assert_eq!(
                catalog
                    .query_sql("SELECT end_snapshot FROM ducklake_view WHERE view_id = 12")
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(5)]]
            );
            for table in [
                "ducklake_column",
                "ducklake_data_file",
                "ducklake_delete_file",
            ] {
                assert_eq!(
                    catalog
                        .query_sql(format!(
                            "SELECT COUNT(*) FROM {table} WHERE table_id = 11 AND end_snapshot IS NULL"
                        ))
                        .await
                        .unwrap()
                        .rows,
                    vec![vec![DuckDbValue::Int64(0)]],
                    "{table}"
                );
            }
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT COUNT(*) FROM ducklake_inlined_data_11_3 WHERE end_snapshot IS NULL"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(0)]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT changes_made FROM ducklake_snapshot_changes ORDER BY snapshot_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![DuckDbValue::Utf8("created_schema:\"analytics\"".into())],
                    vec![DuckDbValue::Utf8(
                        "created_table:\"analytics\".\"events\"".into()
                    )],
                    vec![DuckDbValue::Utf8(
                        "created_view:\"analytics\".\"event_names\"".into()
                    )],
                    vec![DuckDbValue::Utf8(
                        "dropped_schema:10,dropped_table:11,dropped_view:12".into()
                    )],
                ]
            );

            for table in [
                "ducklake_inlined_data_11_3",
                "ducklake_inlined_data_tables",
                "ducklake_delete_file",
                "ducklake_data_file",
                "ducklake_column",
                "ducklake_view",
                "ducklake_table",
                "ducklake_schema",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }
            catalog.disconnect().await.unwrap();
        });
}

#[test]
fn live_quack_create_view_commits_ducklake_metadata_when_enabled() {
    let live_enabled = std::env::var("DUCKLAKE_QUACK_LIVE_TEST")
        .ok()
        .is_some_and(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "on"));
    if !live_enabled {
        eprintln!(
            "skipping live Quack create-view test; set DUCKLAKE_QUACK_LIVE_TEST=1 and run scripts/start-quack-server.sh"
        );
        return;
    }
    let _live_guard = QUACK_LIVE_TEST_LOCK.lock().unwrap();

    let uri =
        std::env::var("DUCKLAKE_QUACK_TEST_URI").unwrap_or_else(|_| "quack:localhost".to_string());
    let token = std::env::var("DUCKLAKE_QUACK_TEST_TOKEN").unwrap_or_else(|_| "asdf".to_string());
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
            let catalog = QuackCatalog::try_new(uri, token).unwrap();
            for table in [
                "ducklake_view",
                "ducklake_table",
                "ducklake_schema",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }

            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot( \
                         snapshot_id BIGINT, snapshot_time TIMESTAMP, schema_version BIGINT, \
                         next_catalog_id BIGINT, next_file_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot_changes( \
                         snapshot_id BIGINT, changes_made VARCHAR, author VARCHAR, \
                         commit_message VARCHAR, commit_extra_info VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_schema( \
                         schema_id BIGINT, schema_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_name VARCHAR, path VARCHAR, \
                         path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_table( \
                         table_id BIGINT, table_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_id BIGINT, table_name VARCHAR, \
                         path VARCHAR, path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_view( \
                         view_id BIGINT, view_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_id BIGINT, view_name VARCHAR, \
                         dialect VARCHAR, sql VARCHAR, column_aliases VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_snapshot VALUES \
                     (1, TIMESTAMP '2026-06-26 00:00:00', 1, 10, 20)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_schema VALUES \
                     (1, '550e8400-e29b-41d4-a716-446655440001'::UUID, 1, NULL, 'main', 'main/', TRUE)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_table VALUES \
                     (2, '550e8400-e29b-41d4-a716-446655440002'::UUID, 1, NULL, 1, 'numbers', 'numbers/', TRUE)",
                )
                .await
                .unwrap();

            let created = catalog
                .commit_create_view(CreateViewCommit {
                    schema_name: "main".into(),
                    view_name: "v_numbers".into(),
                    dialect: "duckdb".into(),
                    sql: "SELECT id, name FROM {DUCKLAKE_CATALOG}.main.numbers".into(),
                    column_aliases: Some("\"id_alias\",\"name_alias\"".into()),
                    commit_message: "quack create view".into(),
                })
                .await
                .unwrap();
            assert_eq!(created, SnapshotId(2));
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT view_id, begin_snapshot, end_snapshot, schema_id, view_name, dialect, sql, column_aliases \
                         FROM ducklake_view WHERE view_name = 'v_numbers'"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(10),
                    DuckDbValue::Int64(2),
                    DuckDbValue::Null,
                    DuckDbValue::Int64(1),
                    DuckDbValue::Utf8("v_numbers".into()),
                    DuckDbValue::Utf8("duckdb".into()),
                    DuckDbValue::Utf8(
                        "SELECT id, name FROM {DUCKLAKE_CATALOG}.main.numbers".into()
                    ),
                    DuckDbValue::Utf8("\"id_alias\",\"name_alias\"".into()),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT snapshot_id, schema_version, next_catalog_id, next_file_id \
                         FROM ducklake_snapshot ORDER BY snapshot_id DESC LIMIT 1"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(2),
                    DuckDbValue::Int64(2),
                    DuckDbValue::Int64(11),
                    DuckDbValue::Int64(20),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT changes_made FROM ducklake_snapshot_changes ORDER BY snapshot_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Utf8(
                    "created_view:\"main\".\"v_numbers\"".into()
                )]]
            );

            for table in [
                "ducklake_view",
                "ducklake_table",
                "ducklake_schema",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }
            catalog.disconnect().await.unwrap();
        });
}

#[test]
fn live_quack_drop_view_commits_ducklake_metadata_when_enabled() {
    let live_enabled = std::env::var("DUCKLAKE_QUACK_LIVE_TEST")
        .ok()
        .is_some_and(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "on"));
    if !live_enabled {
        eprintln!(
            "skipping live Quack drop-view test; set DUCKLAKE_QUACK_LIVE_TEST=1 and run scripts/start-quack-server.sh"
        );
        return;
    }
    let _live_guard = QUACK_LIVE_TEST_LOCK.lock().unwrap();

    let uri =
        std::env::var("DUCKLAKE_QUACK_TEST_URI").unwrap_or_else(|_| "quack:localhost".to_string());
    let token = std::env::var("DUCKLAKE_QUACK_TEST_TOKEN").unwrap_or_else(|_| "asdf".to_string());
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
            let catalog = QuackCatalog::try_new(uri, token).unwrap();
            for table in [
                "ducklake_view",
                "ducklake_schema",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }

            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot( \
                         snapshot_id BIGINT, snapshot_time TIMESTAMP, schema_version BIGINT, \
                         next_catalog_id BIGINT, next_file_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot_changes( \
                         snapshot_id BIGINT, changes_made VARCHAR, author VARCHAR, \
                         commit_message VARCHAR, commit_extra_info VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_schema( \
                         schema_id BIGINT, schema_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_name VARCHAR, path VARCHAR, \
                         path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_view( \
                         view_id BIGINT, view_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_id BIGINT, view_name VARCHAR, \
                         dialect VARCHAR, sql VARCHAR, column_aliases VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_snapshot VALUES \
                     (1, TIMESTAMP '2026-06-26 00:00:00', 1, 10, 20)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_schema VALUES \
                     (1, '550e8400-e29b-41d4-a716-446655440001'::UUID, 1, NULL, 'main', 'main/', TRUE)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_view VALUES \
                     (9, '550e8400-e29b-41d4-a716-446655440002'::UUID, 1, NULL, 1, 'v_numbers', 'duckdb', 'SELECT * FROM numbers', 'id,n')",
                )
                .await
                .unwrap();

            let dropped = catalog
                .commit_drop_view(DropViewCommit {
                    schema_name: "main".into(),
                    view_name: "v_numbers".into(),
                    commit_message: "quack drop view".into(),
                })
                .await
                .unwrap();
            assert_eq!(dropped, SnapshotId(2));
            assert_eq!(
                catalog
                    .query_sql("SELECT end_snapshot FROM ducklake_view WHERE view_id = 9")
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(2)]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT snapshot_id, schema_version, next_catalog_id, next_file_id \
                         FROM ducklake_snapshot ORDER BY snapshot_id DESC LIMIT 1"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(2),
                    DuckDbValue::Int64(2),
                    DuckDbValue::Int64(10),
                    DuckDbValue::Int64(20),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT changes_made FROM ducklake_snapshot_changes ORDER BY snapshot_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Utf8("dropped_view:9".into())]]
            );

            for table in [
                "ducklake_view",
                "ducklake_schema",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }
            catalog.disconnect().await.unwrap();
        });
}

#[test]
fn live_quack_table_and_view_lookup_is_case_insensitive_for_catalog_commits_when_enabled() {
    let live_enabled = std::env::var("DUCKLAKE_QUACK_LIVE_TEST")
        .ok()
        .is_some_and(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "on"));
    if !live_enabled {
        eprintln!(
            "skipping live Quack source-lookup test; set DUCKLAKE_QUACK_LIVE_TEST=1 and run scripts/start-quack-server.sh"
        );
        return;
    }
    let _live_guard = QUACK_LIVE_TEST_LOCK.lock().unwrap();

    let uri =
        std::env::var("DUCKLAKE_QUACK_TEST_URI").unwrap_or_else(|_| "quack:localhost".to_string());
    let token = std::env::var("DUCKLAKE_QUACK_TEST_TOKEN").unwrap_or_else(|_| "asdf".to_string());
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
            let catalog = QuackCatalog::try_new(uri, token).unwrap();
            for table in [
                "ducklake_delete_file",
                "ducklake_data_file",
                "ducklake_column",
                "ducklake_view",
                "ducklake_table",
                "ducklake_schema",
                "ducklake_metadata",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }

            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot( \
                         snapshot_id BIGINT, snapshot_time TIMESTAMP, schema_version BIGINT, \
                         next_catalog_id BIGINT, next_file_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot_changes( \
                         snapshot_id BIGINT, changes_made VARCHAR, author VARCHAR, \
                         commit_message VARCHAR, commit_extra_info VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_metadata( \
                         key VARCHAR, value VARCHAR, scope VARCHAR, scope_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_schema( \
                         schema_id BIGINT, schema_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_name VARCHAR, path VARCHAR, \
                         path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_table( \
                         table_id BIGINT, table_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_id BIGINT, table_name VARCHAR, \
                         path VARCHAR, path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_view( \
                         view_id BIGINT, view_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_id BIGINT, view_name VARCHAR, \
                         dialect VARCHAR, sql VARCHAR, column_aliases VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_column( \
                         column_id BIGINT, begin_snapshot BIGINT, end_snapshot BIGINT, \
                         table_id BIGINT, column_order BIGINT, column_name VARCHAR, \
                         column_type VARCHAR, initial_default VARCHAR, default_value VARCHAR, \
                         nulls_allowed BOOLEAN, parent_column BIGINT, default_value_type VARCHAR, \
                         default_value_dialect VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_data_file( \
                         data_file_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, file_order BIGINT, path VARCHAR, \
                         path_is_relative BOOLEAN, file_format VARCHAR, record_count BIGINT, \
                         file_size_bytes BIGINT, footer_size BIGINT, row_id_start BIGINT, \
                         partition_id BIGINT, encryption_key VARCHAR, mapping_id BIGINT, \
                         partial_max BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_delete_file( \
                         delete_file_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, data_file_id BIGINT, path VARCHAR, \
                         path_is_relative BOOLEAN, format VARCHAR, delete_count BIGINT, \
                         file_size_bytes BIGINT, footer_size BIGINT, encryption_key VARCHAR, \
                         partial_max BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_snapshot VALUES \
                     (1, TIMESTAMP '2026-06-26 00:00:00', 1, 10, 20)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_metadata VALUES \
                     ('version', '1.0', NULL, NULL), \
                     ('data_path', '/tmp/ducklake-data', NULL, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_schema VALUES \
                     (1, '550e8400-e29b-41d4-a716-446655440001'::UUID, 1, NULL, 'main', 'main/', TRUE)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_table VALUES \
                     (2, '550e8400-e29b-41d4-a716-446655440002'::UUID, 1, NULL, 1, 'numbers', 'numbers/', TRUE)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_view VALUES \
                     (3, '550e8400-e29b-41d4-a716-446655440003'::UUID, 1, NULL, 1, 'v_numbers', 'duckdb', 'SELECT * FROM {DUCKLAKE_CATALOG}.numbers', NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_column VALUES \
                     (1, 1, NULL, 2, 1, 'id', 'int32', NULL, 'NULL', TRUE, NULL, 'literal', 'duckdb')",
                )
                .await
                .unwrap();

            let renamed = catalog
                .commit_rename_table(RenameTableCommit {
                    schema_name: "main".into(),
                    old_table_name: "NUMBERS".into(),
                    new_schema_name: None,
                    new_table_name: "renamed".into(),
                    commit_message: "quack rename by case-insensitive source".into(),
                })
                .await
                .unwrap();
            assert_eq!(renamed, SnapshotId(2));

            let dropped = catalog
                .commit_drop_table(DropTableCommit {
                    schema_name: "main".into(),
                    table_name: "RENAMED".into(),
                    commit_message: "quack drop by case-insensitive source".into(),
                })
                .await
                .unwrap();
            assert_eq!(dropped, SnapshotId(3));

            let dropped_view = catalog
                .commit_drop_view(DropViewCommit {
                    schema_name: "main".into(),
                    view_name: "V_NUMBERS".into(),
                    commit_message: "quack drop view by case-insensitive source".into(),
                })
                .await
                .unwrap();
            assert_eq!(dropped_view, SnapshotId(4));

            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT COUNT(*) FROM ducklake_table WHERE end_snapshot IS NULL"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(0)]]
            );
            assert_eq!(
                catalog
                    .query_sql("SELECT COUNT(*) FROM ducklake_view WHERE end_snapshot IS NULL")
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(0)]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT changes_made FROM ducklake_snapshot_changes ORDER BY snapshot_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![DuckDbValue::Utf8(
                        "created_table:\"main\".\"renamed\"".into()
                    )],
                    vec![DuckDbValue::Utf8("dropped_table:2".into())],
                    vec![DuckDbValue::Utf8("dropped_view:3".into())],
                ]
            );

            for table in [
                "ducklake_delete_file",
                "ducklake_data_file",
                "ducklake_column",
                "ducklake_view",
                "ducklake_table",
                "ducklake_schema",
                "ducklake_metadata",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }
            catalog.disconnect().await.unwrap();
        });
}

#[test]
fn live_quack_table_and_view_lookup_prefers_exact_match_and_rejects_ambiguous_case_when_enabled() {
    let live_enabled = std::env::var("DUCKLAKE_QUACK_LIVE_TEST")
        .ok()
        .is_some_and(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "on"));
    if !live_enabled {
        eprintln!(
            "skipping live Quack source-ambiguity test; set DUCKLAKE_QUACK_LIVE_TEST=1 and run scripts/start-quack-server.sh"
        );
        return;
    }
    let _live_guard = QUACK_LIVE_TEST_LOCK.lock().unwrap();

    let uri =
        std::env::var("DUCKLAKE_QUACK_TEST_URI").unwrap_or_else(|_| "quack:localhost".to_string());
    let token = std::env::var("DUCKLAKE_QUACK_TEST_TOKEN").unwrap_or_else(|_| "asdf".to_string());
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
            let catalog = QuackCatalog::try_new(uri, token).unwrap();
            for table in [
                "ducklake_delete_file",
                "ducklake_data_file",
                "ducklake_column",
                "ducklake_view",
                "ducklake_table",
                "ducklake_schema",
                "ducklake_metadata",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }

            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot( \
                         snapshot_id BIGINT, snapshot_time TIMESTAMP, schema_version BIGINT, \
                         next_catalog_id BIGINT, next_file_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot_changes( \
                         snapshot_id BIGINT, changes_made VARCHAR, author VARCHAR, \
                         commit_message VARCHAR, commit_extra_info VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_metadata( \
                         key VARCHAR, value VARCHAR, scope VARCHAR, scope_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_schema( \
                         schema_id BIGINT, schema_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_name VARCHAR, path VARCHAR, \
                         path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_table( \
                         table_id BIGINT, table_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_id BIGINT, table_name VARCHAR, \
                         path VARCHAR, path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_view( \
                         view_id BIGINT, view_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_id BIGINT, view_name VARCHAR, \
                         dialect VARCHAR, sql VARCHAR, column_aliases VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_column( \
                         column_id BIGINT, begin_snapshot BIGINT, end_snapshot BIGINT, \
                         table_id BIGINT, column_order BIGINT, column_name VARCHAR, \
                         column_type VARCHAR, initial_default VARCHAR, default_value VARCHAR, \
                         nulls_allowed BOOLEAN, parent_column BIGINT, default_value_type VARCHAR, \
                         default_value_dialect VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_data_file( \
                         data_file_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, file_order BIGINT, path VARCHAR, \
                         path_is_relative BOOLEAN, file_format VARCHAR, record_count BIGINT, \
                         file_size_bytes BIGINT, footer_size BIGINT, row_id_start BIGINT, \
                         partition_id BIGINT, encryption_key VARCHAR, mapping_id BIGINT, \
                         partial_max BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_delete_file( \
                         delete_file_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, data_file_id BIGINT, path VARCHAR, \
                         path_is_relative BOOLEAN, format VARCHAR, delete_count BIGINT, \
                         file_size_bytes BIGINT, footer_size BIGINT, encryption_key VARCHAR, \
                         partial_max BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_snapshot VALUES \
                     (1, TIMESTAMP '2026-06-26 00:00:00', 1, 10, 20)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_metadata VALUES \
                     ('version', '1.0', NULL, NULL), \
                     ('data_path', '/tmp/ducklake-data', NULL, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_schema VALUES \
                     (1, '550e8400-e29b-41d4-a716-446655440001'::UUID, 1, NULL, 'main', 'main/', TRUE)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_table VALUES \
                     (2, '550e8400-e29b-41d4-a716-446655440002'::UUID, 1, NULL, 1, 'numbers', 'numbers/', TRUE), \
                     (4, '550e8400-e29b-41d4-a716-446655440004'::UUID, 1, NULL, 1, 'NUMBERS', 'upper-numbers/', TRUE)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_view VALUES \
                     (3, '550e8400-e29b-41d4-a716-446655440003'::UUID, 1, NULL, 1, 'v_numbers', 'duckdb', 'SELECT * FROM {DUCKLAKE_CATALOG}.numbers', NULL), \
                     (5, '550e8400-e29b-41d4-a716-446655440005'::UUID, 1, NULL, 1, 'V_NUMBERS', 'duckdb', 'SELECT * FROM {DUCKLAKE_CATALOG}.\"NUMBERS\"', NULL)",
                )
                .await
                .unwrap();

            let ambiguous_table = catalog
                .commit_rename_table(RenameTableCommit {
                    schema_name: "main".into(),
                    old_table_name: "NuMbErS".into(),
                    new_schema_name: None,
                    new_table_name: "renamed".into(),
                    commit_message: "quack ambiguous rename source".into(),
                })
                .await
                .unwrap_err();
            assert!(
                ambiguous_table
                    .to_string()
                    .contains("ambiguous DuckLake table identifier"),
                "{ambiguous_table}"
            );

            let ambiguous_view = catalog
                .commit_drop_view(DropViewCommit {
                    schema_name: "main".into(),
                    view_name: "V_NuMbErS".into(),
                    commit_message: "quack ambiguous drop view source".into(),
                })
                .await
                .unwrap_err();
            assert!(
                ambiguous_view
                    .to_string()
                    .contains("ambiguous DuckLake view identifier"),
                "{ambiguous_view}"
            );
            assert_eq!(
                catalog
                    .query_sql("SELECT MAX(snapshot_id) FROM ducklake_snapshot")
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(1)]]
            );

            let dropped_table = catalog
                .commit_drop_table(DropTableCommit {
                    schema_name: "main".into(),
                    table_name: "NUMBERS".into(),
                    commit_message: "quack drop exact uppercase source".into(),
                })
                .await
                .unwrap();
            assert_eq!(dropped_table, SnapshotId(2));

            let dropped_view = catalog
                .commit_drop_view(DropViewCommit {
                    schema_name: "main".into(),
                    view_name: "V_NUMBERS".into(),
                    commit_message: "quack drop exact uppercase view source".into(),
                })
                .await
                .unwrap();
            assert_eq!(dropped_view, SnapshotId(3));
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT table_id, table_name FROM ducklake_table \
                         WHERE end_snapshot IS NULL ORDER BY table_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(2),
                    DuckDbValue::Utf8("numbers".into())
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT view_id, view_name FROM ducklake_view \
                         WHERE end_snapshot IS NULL ORDER BY view_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(3),
                    DuckDbValue::Utf8("v_numbers".into())
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT changes_made FROM ducklake_snapshot_changes ORDER BY snapshot_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![DuckDbValue::Utf8("dropped_table:4".into())],
                    vec![DuckDbValue::Utf8("dropped_view:5".into())],
                ]
            );

            for table in [
                "ducklake_delete_file",
                "ducklake_data_file",
                "ducklake_column",
                "ducklake_view",
                "ducklake_table",
                "ducklake_schema",
                "ducklake_metadata",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }
            catalog.disconnect().await.unwrap();
        });
}

#[test]
fn live_quack_schema_lookup_prefers_exact_match_and_rejects_ambiguous_case_when_enabled() {
    let live_enabled = std::env::var("DUCKLAKE_QUACK_LIVE_TEST")
        .ok()
        .is_some_and(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "on"));
    if !live_enabled {
        eprintln!(
            "skipping live Quack schema-lookup test; set DUCKLAKE_QUACK_LIVE_TEST=1 and run scripts/start-quack-server.sh"
        );
        return;
    }
    let _live_guard = QUACK_LIVE_TEST_LOCK.lock().unwrap();

    let uri =
        std::env::var("DUCKLAKE_QUACK_TEST_URI").unwrap_or_else(|_| "quack:localhost".to_string());
    let token = std::env::var("DUCKLAKE_QUACK_TEST_TOKEN").unwrap_or_else(|_| "asdf".to_string());
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
            let catalog = QuackCatalog::try_new(uri, token).unwrap();
            for table in [
                "ducklake_column",
                "ducklake_table",
                "ducklake_schema",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }

            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot( \
                         snapshot_id BIGINT, snapshot_time TIMESTAMP, schema_version BIGINT, \
                         next_catalog_id BIGINT, next_file_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot_changes( \
                         snapshot_id BIGINT, changes_made VARCHAR, author VARCHAR, \
                         commit_message VARCHAR, commit_extra_info VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_schema( \
                         schema_id BIGINT, schema_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_name VARCHAR, path VARCHAR, \
                         path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_table( \
                         table_id BIGINT, table_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_id BIGINT, table_name VARCHAR, \
                         path VARCHAR, path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_column( \
                         column_id BIGINT, begin_snapshot BIGINT, end_snapshot BIGINT, \
                         table_id BIGINT, column_order BIGINT, column_name VARCHAR, \
                         column_type VARCHAR, initial_default VARCHAR, default_value VARCHAR, \
                         nulls_allowed BOOLEAN, parent_column BIGINT, default_value_type VARCHAR, \
                         default_value_dialect VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_snapshot VALUES \
                     (1, TIMESTAMP '2026-06-26 00:00:00', 1, 10, 20)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_schema VALUES \
                     (1, '550e8400-e29b-41d4-a716-446655440001'::UUID, 1, NULL, 'main', 'main/', TRUE), \
                     (42, '550e8400-e29b-41d4-a716-446655440042'::UUID, 1, NULL, 'MAIN', 'MAIN/', TRUE)",
                )
                .await
                .unwrap();

            let ambiguous = catalog
                .commit_create_table(CreateTableCommit {
                    schema_name: "mAiN".into(),
                    table_name: "ambiguous".into(),
                    columns: vec![CreateTableColumn {
                        column_id: ColumnId(1),
                        column_order: 1,
                        column_name: "id".into(),
                        column_type: "int32".into(),
                        nulls_allowed: true,
                        parent_column: None,
                    }],
                    commit_message: "quack ambiguous schema lookup".into(),
                })
                .await
                .unwrap_err();
            assert!(
                ambiguous
                    .to_string()
                    .contains("ambiguous DuckLake schema identifier"),
                "{ambiguous}"
            );
            assert_eq!(
                catalog
                    .query_sql("SELECT COUNT(*) FROM ducklake_snapshot")
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(1)]]
            );

            let created = catalog
                .commit_create_table(CreateTableCommit {
                    schema_name: "MAIN".into(),
                    table_name: "exact_upper".into(),
                    columns: vec![CreateTableColumn {
                        column_id: ColumnId(1),
                        column_order: 1,
                        column_name: "id".into(),
                        column_type: "int32".into(),
                        nulls_allowed: true,
                        parent_column: None,
                    }],
                    commit_message: "quack exact uppercase schema lookup".into(),
                })
                .await
                .unwrap();
            assert_eq!(created, SnapshotId(2));
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT schema_id FROM ducklake_table WHERE table_name = 'exact_upper'"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(42)]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT changes_made FROM ducklake_snapshot_changes ORDER BY snapshot_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Utf8(
                    "created_table:\"MAIN\".\"exact_upper\"".into()
                )]]
            );

            for table in [
                "ducklake_column",
                "ducklake_table",
                "ducklake_schema",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }
            catalog.disconnect().await.unwrap();
        });
}

#[test]
fn live_quack_partitioning_batch_matches_duckdb_multi_table_transaction_when_enabled() {
    let live_enabled = std::env::var("DUCKLAKE_QUACK_LIVE_TEST")
        .ok()
        .is_some_and(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "on"));
    if !live_enabled {
        eprintln!(
            "skipping live Quack partitioning test; set DUCKLAKE_QUACK_LIVE_TEST=1 and run scripts/start-quack-server.sh"
        );
        return;
    }
    let _live_guard = QUACK_LIVE_TEST_LOCK.lock().unwrap();

    let uri =
        std::env::var("DUCKLAKE_QUACK_TEST_URI").unwrap_or_else(|_| "quack:localhost".to_string());
    let token = std::env::var("DUCKLAKE_QUACK_TEST_TOKEN").unwrap_or_else(|_| "asdf".to_string());
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
            let catalog = QuackCatalog::try_new(uri, token).unwrap();
            for table in [
                "ducklake_partition_column",
                "ducklake_partition_info",
                "ducklake_column",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }

            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot( \
                         snapshot_id BIGINT, snapshot_time TIMESTAMP, schema_version BIGINT, \
                         next_catalog_id BIGINT, next_file_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot_changes( \
                         snapshot_id BIGINT, changes_made VARCHAR, author VARCHAR, \
                         commit_message VARCHAR, commit_extra_info VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_column( \
                         column_id BIGINT, begin_snapshot BIGINT, end_snapshot BIGINT, \
                         table_id BIGINT, column_order BIGINT, column_name VARCHAR, \
                         column_type VARCHAR, initial_default VARCHAR, default_value VARCHAR, \
                         nulls_allowed BOOLEAN, parent_column BIGINT, default_value_type VARCHAR, \
                         default_value_dialect VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_partition_info( \
                         partition_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_partition_column( \
                         partition_id BIGINT, table_id BIGINT, partition_key_index BIGINT, \
                         column_id BIGINT, transform VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_snapshot VALUES \
                     (2, TIMESTAMP '2026-06-26 00:00:00', 2, 3, 0)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_column VALUES \
                     (1, 1, NULL, 1, 1, 'part_key', 'int32', NULL, NULL, TRUE, NULL, 'literal', 'duckdb'), \
                     (2, 1, NULL, 1, 2, 'values', 'varchar', NULL, NULL, TRUE, NULL, 'literal', 'duckdb'), \
                     (1, 2, NULL, 2, 1, 'part_key', 'int32', NULL, NULL, TRUE, NULL, 'literal', 'duckdb'), \
                     (2, 2, NULL, 2, 2, 'values', 'varchar', NULL, NULL, TRUE, NULL, 'literal', 'duckdb')",
                )
                .await
                .unwrap();

            let snapshot = catalog
                .commit_partitioning_batch(PartitioningBatchCommit {
                    expected_snapshot: SnapshotId(2),
                    changes: vec![
                        PartitioningTableCommit {
                            table_id: TableId(1),
                            columns: vec![PartitionColumnCommit {
                                column_id: ColumnId(1),
                                transform: PartitionTransform::Identity,
                            }],
                        },
                        PartitioningTableCommit {
                            table_id: TableId(2),
                            columns: vec![PartitionColumnCommit {
                                column_id: ColumnId(1),
                                transform: PartitionTransform::Identity,
                            }],
                        },
                    ],
                    commit_message: "quack set partitioning for two tables".into(),
                })
                .await
                .unwrap();
            assert_eq!(snapshot, SnapshotId(3));
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT partition_id, begin_snapshot, end_snapshot, table_id \
                         FROM ducklake_partition_info ORDER BY partition_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![
                        DuckDbValue::Int64(3),
                        DuckDbValue::Int64(3),
                        DuckDbValue::Null,
                        DuckDbValue::Int64(2),
                    ],
                    vec![
                        DuckDbValue::Int64(4),
                        DuckDbValue::Int64(3),
                        DuckDbValue::Null,
                        DuckDbValue::Int64(1),
                    ],
                ]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT partition_id, partition_key_index, column_id, transform \
                         FROM ducklake_partition_column ORDER BY partition_id, partition_key_index"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![
                        DuckDbValue::Int64(3),
                        DuckDbValue::Int64(0),
                        DuckDbValue::Int64(1),
                        DuckDbValue::Utf8("identity".into()),
                    ],
                    vec![
                        DuckDbValue::Int64(4),
                        DuckDbValue::Int64(0),
                        DuckDbValue::Int64(1),
                        DuckDbValue::Utf8("identity".into()),
                    ],
                ]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT snapshot_id, schema_version, next_catalog_id, next_file_id \
                         FROM ducklake_snapshot ORDER BY snapshot_id DESC LIMIT 1"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(3),
                    DuckDbValue::Int64(3),
                    DuckDbValue::Int64(5),
                    DuckDbValue::Int64(0),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT changes_made FROM ducklake_snapshot_changes ORDER BY snapshot_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Utf8(
                    "altered_table:1,altered_table:2".into()
                )]]
            );

            for table in [
                "ducklake_partition_column",
                "ducklake_partition_info",
                "ducklake_column",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }
            catalog.disconnect().await.unwrap();
        });
}

#[test]
fn live_quack_add_column_default_commits_column_and_table_stats_when_enabled() {
    let live_enabled = std::env::var("DUCKLAKE_QUACK_LIVE_TEST")
        .ok()
        .is_some_and(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "on"));
    if !live_enabled {
        eprintln!(
            "skipping live Quack add-column test; set DUCKLAKE_QUACK_LIVE_TEST=1 and run scripts/start-quack-server.sh"
        );
        return;
    }
    let _live_guard = QUACK_LIVE_TEST_LOCK.lock().unwrap();

    let uri =
        std::env::var("DUCKLAKE_QUACK_TEST_URI").unwrap_or_else(|_| "quack:localhost".to_string());
    let token = std::env::var("DUCKLAKE_QUACK_TEST_TOKEN").unwrap_or_else(|_| "asdf".to_string());
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
            let catalog = QuackCatalog::try_new(uri, token).unwrap();
            for table in [
                "ducklake_table_column_stats",
                "ducklake_column",
                "ducklake_table",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot( \
                         snapshot_id BIGINT, snapshot_time TIMESTAMP, schema_version BIGINT, \
                         next_catalog_id BIGINT, next_file_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot_changes( \
                         snapshot_id BIGINT, changes_made VARCHAR, author VARCHAR, \
                         commit_message VARCHAR, commit_extra_info VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_table( \
                         table_id BIGINT, table_uuid UUID, begin_snapshot BIGINT, end_snapshot BIGINT, \
                         schema_id BIGINT, table_name VARCHAR, path VARCHAR, path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_column( \
                         column_id BIGINT, begin_snapshot BIGINT, end_snapshot BIGINT, \
                         table_id BIGINT, column_order BIGINT, column_name VARCHAR, \
                         column_type VARCHAR, initial_default VARCHAR, default_value VARCHAR, \
                         nulls_allowed BOOLEAN, parent_column BIGINT, default_value_type VARCHAR, \
                         default_value_dialect VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_table_column_stats( \
                         table_id BIGINT, column_id BIGINT, contains_null BOOLEAN, \
                         contains_nan BOOLEAN, min_value VARCHAR, max_value VARCHAR, \
                         extra_stats VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_snapshot VALUES \
                     (1, TIMESTAMP '2026-06-26 00:00:00', 1, 3, 10)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_table VALUES \
                     (1, '550e8400-e29b-41d4-a716-446655440001'::UUID, 1, NULL, 1, 'numbers', 'numbers/', TRUE)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_column VALUES \
                     (1, 1, NULL, 1, 1, 'id', 'int32', NULL, NULL, TRUE, NULL, 'literal', 'duckdb'), \
                     (2, 1, NULL, 1, 2, 'name', 'varchar', NULL, NULL, TRUE, NULL, 'literal', 'duckdb')",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_table_column_stats VALUES \
                     (1, 1, FALSE, FALSE, '1', '3', NULL), \
                     (1, 2, FALSE, NULL, 'a', 'c', NULL)",
                )
                .await
                .unwrap();

            let snapshot = catalog
                .commit_add_columns(AddColumnCommit {
                    table_id: TableId(1),
                    expected_snapshot: SnapshotId(1),
                    columns: vec![AddColumn {
                        column_id: ColumnId(3),
                        column_order: 3,
                        column_name: "note".into(),
                        column_type: "varchar".into(),
                        initial_default: Some("hello".into()),
                        default_value: Some("hello".into()),
                        nulls_allowed: true,
                        parent_column: None,
                        default_value_type: Some("literal".into()),
                        default_value_dialect: Some("duckdb".into()),
                    }],
                    table_column_stats: vec![TableColumnStats {
                        table_id: TableId(1),
                        column_id: ColumnId(3),
                        contains_null: Some(false),
                        contains_nan: None,
                        min_value: Some("hello".into()),
                        max_value: Some("hello".into()),
                        extra_stats: None,
                    }],
                    commit_message: "quack add defaulted column".into(),
                })
                .await
                .unwrap();
            assert_eq!(snapshot, SnapshotId(2));

            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT snapshot_id, schema_version, next_catalog_id, next_file_id \
                         FROM ducklake_snapshot ORDER BY snapshot_id DESC LIMIT 1"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(2),
                    DuckDbValue::Int64(2),
                    DuckDbValue::Int64(4),
                    DuckDbValue::Int64(10),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT column_id, begin_snapshot, end_snapshot, column_order, column_name, \
                                column_type, initial_default, default_value, nulls_allowed, parent_column, \
                                default_value_type, default_value_dialect \
                         FROM ducklake_column WHERE column_id = 3"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(3),
                    DuckDbValue::Int64(2),
                    DuckDbValue::Null,
                    DuckDbValue::Int64(3),
                    DuckDbValue::Utf8("note".into()),
                    DuckDbValue::Utf8("varchar".into()),
                    DuckDbValue::Utf8("hello".into()),
                    DuckDbValue::Utf8("hello".into()),
                    DuckDbValue::Boolean(true),
                    DuckDbValue::Null,
                    DuckDbValue::Utf8("literal".into()),
                    DuckDbValue::Utf8("duckdb".into()),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT contains_null, contains_nan, min_value, max_value \
                         FROM ducklake_table_column_stats WHERE table_id = 1 AND column_id = 3"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Boolean(false),
                    DuckDbValue::Null,
                    DuckDbValue::Utf8("hello".into()),
                    DuckDbValue::Utf8("hello".into()),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT changes_made FROM ducklake_snapshot_changes WHERE snapshot_id = 2"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Utf8("altered_table:1".into())]]
            );
            catalog.disconnect().await.unwrap();
        });
}

#[test]
fn live_quack_sorted_by_commit_updates_metadata_when_enabled() {
    let live_enabled = std::env::var("DUCKLAKE_QUACK_LIVE_TEST")
        .ok()
        .is_some_and(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "on"));
    if !live_enabled {
        eprintln!(
            "skipping live Quack sorted metadata test; set DUCKLAKE_QUACK_LIVE_TEST=1 and run scripts/start-quack-server.sh"
        );
        return;
    }
    let _live_guard = QUACK_LIVE_TEST_LOCK.lock().unwrap();

    let uri =
        std::env::var("DUCKLAKE_QUACK_TEST_URI").unwrap_or_else(|_| "quack:localhost".to_string());
    let token = std::env::var("DUCKLAKE_QUACK_TEST_TOKEN").unwrap_or_else(|_| "asdf".to_string());
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
            let catalog = QuackCatalog::try_new(uri, token).unwrap();
            for table in [
                "ducklake_sort_expression",
                "ducklake_sort_info",
                "ducklake_column",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot( \
                         snapshot_id BIGINT, snapshot_time TIMESTAMP, schema_version BIGINT, \
                         next_catalog_id BIGINT, next_file_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot_changes( \
                         snapshot_id BIGINT, changes_made VARCHAR, author VARCHAR, \
                         commit_message VARCHAR, commit_extra_info VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_column( \
                         column_id BIGINT, begin_snapshot BIGINT, end_snapshot BIGINT, \
                         table_id BIGINT, column_order BIGINT, column_name VARCHAR, \
                         column_type VARCHAR, initial_default VARCHAR, default_value VARCHAR, \
                         nulls_allowed BOOLEAN, parent_column BIGINT, default_value_type VARCHAR, \
                         default_value_dialect VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_sort_info( \
                         sort_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, end_snapshot BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_sort_expression( \
                         sort_id BIGINT, table_id BIGINT, sort_key_index BIGINT, expression VARCHAR, \
                         dialect VARCHAR, sort_direction VARCHAR, null_order VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_snapshot VALUES \
                     (2, TIMESTAMP '2026-06-26 00:00:00', 7, 10, 20)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_column VALUES \
                     (1, 1, NULL, 1, 1, 'id', 'int32', NULL, NULL, TRUE, NULL, NULL, NULL), \
                     (2, 1, NULL, 1, 2, 'name', 'varchar', NULL, NULL, TRUE, NULL, NULL, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql("INSERT INTO ducklake_sort_info VALUES (5, 1, 1, NULL)")
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_sort_expression VALUES \
                     (5, 1, 0, 'id', 'duckdb', 'ASC', 'NULLS_LAST')",
                )
                .await
                .unwrap();

            assert_eq!(
                catalog
                    .commit_sorted_by(SortedByCommit {
                        table_id: TableId(1),
                        expected_snapshot: SnapshotId(2),
                        expressions: vec![SortExpressionCommit {
                            expression: "name".into(),
                            dialect: "duckdb".into(),
                            sort_direction: "DESC".into(),
                            null_order: "NULLS_FIRST".into(),
                        }],
                        commit_message: "quack set sorted".into(),
                    })
                    .await
                    .unwrap(),
                SnapshotId(3)
            );
            assert_eq!(
                catalog
                    .commit_sorted_by(SortedByCommit {
                        table_id: TableId(1),
                        expected_snapshot: SnapshotId(3),
                        expressions: vec![SortExpressionCommit {
                            expression: "name".into(),
                            dialect: "duckdb".into(),
                            sort_direction: "DESC".into(),
                            null_order: "NULLS_FIRST".into(),
                        }],
                        commit_message: "quack set sorted again".into(),
                    })
                    .await
                    .unwrap(),
                SnapshotId(3)
            );
            assert_eq!(
                catalog
                    .commit_sorted_by(SortedByCommit {
                        table_id: TableId(1),
                        expected_snapshot: SnapshotId(3),
                        expressions: Vec::new(),
                        commit_message: "quack reset sorted".into(),
                    })
                    .await
                    .unwrap(),
                SnapshotId(4)
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT snapshot_id, schema_version, next_catalog_id, next_file_id \
                         FROM ducklake_snapshot ORDER BY snapshot_id DESC LIMIT 1"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(4),
                    DuckDbValue::Int64(7),
                    DuckDbValue::Int64(11),
                    DuckDbValue::Int64(20),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT si.sort_id, si.begin_snapshot, si.end_snapshot, se.expression, \
                                se.sort_direction, se.null_order \
                         FROM ducklake_sort_info si \
                         JOIN ducklake_sort_expression se USING (sort_id, table_id) \
                         ORDER BY si.sort_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![
                        DuckDbValue::Int64(5),
                        DuckDbValue::Int64(1),
                        DuckDbValue::Int64(3),
                        DuckDbValue::Utf8("id".into()),
                        DuckDbValue::Utf8("ASC".into()),
                        DuckDbValue::Utf8("NULLS_LAST".into()),
                    ],
                    vec![
                        DuckDbValue::Int64(10),
                        DuckDbValue::Int64(3),
                        DuckDbValue::Int64(4),
                        DuckDbValue::Utf8("name".into()),
                        DuckDbValue::Utf8("DESC".into()),
                        DuckDbValue::Utf8("NULLS_FIRST".into()),
                    ],
                ]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT changes_made FROM ducklake_snapshot_changes ORDER BY snapshot_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![DuckDbValue::Utf8("altered_table:1".into())],
                    vec![DuckDbValue::Utf8("altered_table:1".into())],
                ]
            );
            catalog.disconnect().await.unwrap();
        });
}

#[test]
fn live_quack_column_default_and_rename_column_commits_metadata_when_enabled() {
    let live_enabled = std::env::var("DUCKLAKE_QUACK_LIVE_TEST")
        .ok()
        .is_some_and(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "on"));
    if !live_enabled {
        eprintln!(
            "skipping live Quack column-alter test; set DUCKLAKE_QUACK_LIVE_TEST=1 and run scripts/start-quack-server.sh"
        );
        return;
    }
    let _live_guard = QUACK_LIVE_TEST_LOCK.lock().unwrap();

    let uri =
        std::env::var("DUCKLAKE_QUACK_TEST_URI").unwrap_or_else(|_| "quack:localhost".to_string());
    let token = std::env::var("DUCKLAKE_QUACK_TEST_TOKEN").unwrap_or_else(|_| "asdf".to_string());
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
            let catalog = QuackCatalog::try_new(uri, token).unwrap();
            for table in [
                "ducklake_sort_expression",
                "ducklake_sort_info",
                "ducklake_column",
                "ducklake_table",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot( \
                         snapshot_id BIGINT, snapshot_time TIMESTAMP, schema_version BIGINT, \
                         next_catalog_id BIGINT, next_file_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot_changes( \
                         snapshot_id BIGINT, changes_made VARCHAR, author VARCHAR, \
                         commit_message VARCHAR, commit_extra_info VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_table( \
                         table_id BIGINT, table_uuid UUID, begin_snapshot BIGINT, end_snapshot BIGINT, \
                         schema_id BIGINT, table_name VARCHAR, path VARCHAR, path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_column( \
                         column_id BIGINT, begin_snapshot BIGINT, end_snapshot BIGINT, \
                         table_id BIGINT, column_order BIGINT, column_name VARCHAR, \
                         column_type VARCHAR, initial_default VARCHAR, default_value VARCHAR, \
                         nulls_allowed BOOLEAN, parent_column BIGINT, default_value_type VARCHAR, \
                         default_value_dialect VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_sort_info( \
                         sort_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, end_snapshot BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_sort_expression( \
                         sort_id BIGINT, table_id BIGINT, sort_key_index BIGINT, expression VARCHAR, \
                         dialect VARCHAR, sort_direction VARCHAR, null_order VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_snapshot VALUES \
                     (1, TIMESTAMP '2026-06-26 00:00:00', 1, 10, 20)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_table VALUES \
                     (1, '550e8400-e29b-41d4-a716-446655440001'::UUID, 1, NULL, 1, 'numbers', 'numbers/', TRUE)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_column VALUES \
                     (1, 1, NULL, 1, 1, 'id', 'int32', NULL, NULL, TRUE, NULL, NULL, NULL), \
                     (2, 1, NULL, 1, 2, 'name', 'varchar', NULL, NULL, TRUE, NULL, NULL, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql("INSERT INTO ducklake_sort_info VALUES (5, 1, 1, NULL)")
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_sort_expression VALUES \
                     (5, 1, 0, 'name || id', 'duckdb', 'ASC', 'NULLS_LAST')",
                )
                .await
                .unwrap();

            assert_eq!(
                catalog
                    .commit_column_default(ColumnDefaultCommit {
                        table_id: TableId(1),
                        expected_snapshot: SnapshotId(1),
                        column_id: ColumnId(2),
                        default_value: Some("guest".into()),
                        default_value_type: Some("literal".into()),
                        default_value_dialect: Some("duckdb".into()),
                        commit_message: "quack set default".into(),
                    })
                    .await
                    .unwrap(),
                SnapshotId(2)
            );
            assert_eq!(
                catalog
                    .commit_column_default(ColumnDefaultCommit {
                        table_id: TableId(1),
                        expected_snapshot: SnapshotId(2),
                        column_id: ColumnId(2),
                        default_value: None,
                        default_value_type: None,
                        default_value_dialect: None,
                        commit_message: "quack drop default".into(),
                    })
                    .await
                    .unwrap(),
                SnapshotId(3)
            );
            assert_eq!(
                catalog
                    .commit_rename_column(RenameColumnCommit {
                        table_id: TableId(1),
                        expected_snapshot: SnapshotId(3),
                        column_id: ColumnId(2),
                        new_column_name: "label".into(),
                        commit_message: "quack rename column".into(),
                    })
                    .await
                    .unwrap(),
                SnapshotId(4)
            );

            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT snapshot_id, schema_version, next_catalog_id, next_file_id \
                         FROM ducklake_snapshot ORDER BY snapshot_id DESC LIMIT 1"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(4),
                    DuckDbValue::Int64(4),
                    DuckDbValue::Int64(11),
                    DuckDbValue::Int64(20),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT column_name, begin_snapshot, end_snapshot, default_value, \
                                default_value_type, default_value_dialect \
                         FROM ducklake_column WHERE column_id = 2 ORDER BY begin_snapshot"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![
                        DuckDbValue::Utf8("name".into()),
                        DuckDbValue::Int64(1),
                        DuckDbValue::Int64(2),
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                    ],
                    vec![
                        DuckDbValue::Utf8("name".into()),
                        DuckDbValue::Int64(2),
                        DuckDbValue::Int64(3),
                        DuckDbValue::Utf8("guest".into()),
                        DuckDbValue::Utf8("literal".into()),
                        DuckDbValue::Utf8("duckdb".into()),
                    ],
                    vec![
                        DuckDbValue::Utf8("name".into()),
                        DuckDbValue::Int64(3),
                        DuckDbValue::Int64(4),
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                    ],
                    vec![
                        DuckDbValue::Utf8("label".into()),
                        DuckDbValue::Int64(4),
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                    ],
                ]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT si.sort_id, si.begin_snapshot, si.end_snapshot, se.expression \
                         FROM ducklake_sort_info si \
                         JOIN ducklake_sort_expression se USING (sort_id, table_id) \
                         ORDER BY si.sort_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![
                        DuckDbValue::Int64(5),
                        DuckDbValue::Int64(1),
                        DuckDbValue::Int64(4),
                        DuckDbValue::Utf8("name || id".into()),
                    ],
                    vec![
                        DuckDbValue::Int64(10),
                        DuckDbValue::Int64(4),
                        DuckDbValue::Null,
                        DuckDbValue::Utf8("label || id".into()),
                    ],
                ]
            );
            let sorted_drop_err = catalog
                .commit_drop_column(DropColumnCommit {
                    table_id: TableId(1),
                    expected_snapshot: SnapshotId(4),
                    column_id: ColumnId(2),
                    commit_message: "quack drop sorted column".into(),
                })
                .await
                .unwrap_err();
            assert!(
                sorted_drop_err.to_string().contains("sorted by this column"),
                "{sorted_drop_err}"
            );
            catalog
                .query_sql("UPDATE ducklake_sort_expression SET expression = 'id' WHERE sort_id = 10 AND table_id = 1")
                .await
                .unwrap();
            assert_eq!(
                catalog
                    .commit_drop_column(DropColumnCommit {
                        table_id: TableId(1),
                        expected_snapshot: SnapshotId(4),
                        column_id: ColumnId(2),
                        commit_message: "quack drop column".into(),
                    })
                    .await
                    .unwrap(),
                SnapshotId(5)
            );
            assert_eq!(
                catalog
                    .commit_column_type(ColumnTypeCommit {
                        table_id: TableId(1),
                        expected_snapshot: SnapshotId(5),
                        column_id: ColumnId(1),
                        new_column_type: "int64".into(),
                        commit_message: "quack promote column".into(),
                    })
                    .await
                    .unwrap(),
                SnapshotId(6)
            );
            assert_eq!(
                catalog
                    .commit_column_type(ColumnTypeCommit {
                        table_id: TableId(1),
                        expected_snapshot: SnapshotId(6),
                        column_id: ColumnId(1),
                        new_column_type: "int64".into(),
                        commit_message: "quack same type".into(),
                    })
                    .await
                    .unwrap(),
                SnapshotId(6)
            );
            let narrow_err = catalog
                .commit_column_type(ColumnTypeCommit {
                    table_id: TableId(1),
                    expected_snapshot: SnapshotId(6),
                    column_id: ColumnId(1),
                    new_column_type: "int16".into(),
                    commit_message: "quack narrow column".into(),
                })
                .await
                .unwrap_err();
            assert!(
                narrow_err.to_string().contains("only widening"),
                "{narrow_err}"
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT snapshot_id, schema_version, next_catalog_id, next_file_id \
                         FROM ducklake_snapshot ORDER BY snapshot_id DESC LIMIT 1"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(6),
                    DuckDbValue::Int64(6),
                    DuckDbValue::Int64(11),
                    DuckDbValue::Int64(20),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT column_name FROM ducklake_column \
                         WHERE table_id = 1 AND end_snapshot IS NULL ORDER BY column_order"
                    )
                    .await
                .unwrap()
                .rows,
                vec![vec![DuckDbValue::Utf8("id".into())]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT begin_snapshot, end_snapshot, column_type \
                         FROM ducklake_column \
                         WHERE table_id = 1 AND column_id = 1 ORDER BY begin_snapshot"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![
                        DuckDbValue::Int64(1),
                        DuckDbValue::Int64(6),
                        DuckDbValue::Utf8("int32".into()),
                    ],
                    vec![
                        DuckDbValue::Int64(6),
                        DuckDbValue::Null,
                        DuckDbValue::Utf8("int64".into()),
                    ],
                ]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT changes_made FROM ducklake_snapshot_changes ORDER BY snapshot_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![DuckDbValue::Utf8("altered_table:1".into())],
                    vec![DuckDbValue::Utf8("altered_table:1".into())],
                    vec![DuckDbValue::Utf8("altered_table:1".into())],
                    vec![DuckDbValue::Utf8("altered_table:1".into())],
                    vec![DuckDbValue::Utf8("altered_table:1".into())],
                ]
            );
            catalog.disconnect().await.unwrap();
        });
}

#[test]
fn live_quack_column_type_tree_commit_replaces_struct_children_when_enabled() {
    let live_enabled = std::env::var("DUCKLAKE_QUACK_LIVE_TEST")
        .ok()
        .is_some_and(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "on"));
    if !live_enabled {
        eprintln!(
            "skipping live Quack column-type-tree test; set DUCKLAKE_QUACK_LIVE_TEST=1 and run scripts/start-quack-server.sh"
        );
        return;
    }
    let _live_guard = QUACK_LIVE_TEST_LOCK.lock().unwrap();

    let uri =
        std::env::var("DUCKLAKE_QUACK_TEST_URI").unwrap_or_else(|_| "quack:localhost".to_string());
    let token = std::env::var("DUCKLAKE_QUACK_TEST_TOKEN").unwrap_or_else(|_| "asdf".to_string());
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
            let catalog = QuackCatalog::try_new(uri, token).unwrap();
            for table in [
                "ducklake_column",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot( \
                         snapshot_id BIGINT, snapshot_time TIMESTAMP, schema_version BIGINT, \
                         next_catalog_id BIGINT, next_file_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot_changes( \
                         snapshot_id BIGINT, changes_made VARCHAR, author VARCHAR, \
                         commit_message VARCHAR, commit_extra_info VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_column( \
                         column_id BIGINT, begin_snapshot BIGINT, end_snapshot BIGINT, \
                         table_id BIGINT, column_order BIGINT, column_name VARCHAR, \
                         column_type VARCHAR, initial_default VARCHAR, default_value VARCHAR, \
                         nulls_allowed BOOLEAN, parent_column BIGINT, default_value_type VARCHAR, \
                         default_value_dialect VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_snapshot VALUES \
                     (1, TIMESTAMP '2026-06-26 00:00:00', 1, 10, 20)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_column VALUES \
                     (3, 1, NULL, 1, 3, 'profile', 'struct', NULL, NULL, TRUE, NULL, NULL, NULL), \
                     (4, 1, NULL, 1, 0, 'age', 'int32', NULL, NULL, TRUE, 3, NULL, NULL), \
                     (5, 1, NULL, 1, 1, 'city', 'varchar', NULL, NULL, TRUE, 3, NULL, NULL)",
                )
                .await
                .unwrap();

            let replacement = crate::ColumnTypeTree {
                column_name: "profile".into(),
                column_type: "struct".into(),
                nulls_allowed: true,
                children: vec![
                    crate::ColumnTypeTree {
                        column_name: "age".into(),
                        column_type: "int64".into(),
                        nulls_allowed: true,
                        children: vec![],
                    },
                    crate::ColumnTypeTree {
                        column_name: "city".into(),
                        column_type: "varchar".into(),
                        nulls_allowed: true,
                        children: vec![],
                    },
                    crate::ColumnTypeTree {
                        column_name: "score".into(),
                        column_type: "int32".into(),
                        nulls_allowed: true,
                        children: vec![],
                    },
                ],
            };
            assert_eq!(
                catalog
                    .commit_column_type_tree(ColumnTypeTreeCommit {
                        table_id: TableId(1),
                        expected_snapshot: SnapshotId(1),
                        column_id: ColumnId(3),
                        replacement: replacement.clone(),
                        commit_message: "quack replace struct".into(),
                    })
                    .await
                    .unwrap(),
                SnapshotId(2)
            );
            assert_eq!(
                catalog
                    .commit_column_type_tree(ColumnTypeTreeCommit {
                        table_id: TableId(1),
                        expected_snapshot: SnapshotId(2),
                        column_id: ColumnId(3),
                        replacement,
                        commit_message: "quack same struct".into(),
                    })
                    .await
                    .unwrap(),
                SnapshotId(2)
            );

            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT column_id, begin_snapshot, end_snapshot, column_order, column_name, \
                                column_type, parent_column, default_value, default_value_type, default_value_dialect \
                         FROM ducklake_column \
                         WHERE table_id = 1 AND column_id IN (3, 4, 5, 10) \
                         ORDER BY column_id, begin_snapshot"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![
                        DuckDbValue::Int64(3),
                        DuckDbValue::Int64(1),
                        DuckDbValue::Int64(2),
                        DuckDbValue::Int64(3),
                        DuckDbValue::Utf8("profile".into()),
                        DuckDbValue::Utf8("struct".into()),
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                    ],
                    vec![
                        DuckDbValue::Int64(3),
                        DuckDbValue::Int64(2),
                        DuckDbValue::Null,
                        DuckDbValue::Int64(3),
                        DuckDbValue::Utf8("profile".into()),
                        DuckDbValue::Utf8("struct".into()),
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                    ],
                    vec![
                        DuckDbValue::Int64(4),
                        DuckDbValue::Int64(1),
                        DuckDbValue::Int64(2),
                        DuckDbValue::Int64(0),
                        DuckDbValue::Utf8("age".into()),
                        DuckDbValue::Utf8("int32".into()),
                        DuckDbValue::Int64(3),
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                    ],
                    vec![
                        DuckDbValue::Int64(4),
                        DuckDbValue::Int64(2),
                        DuckDbValue::Null,
                        DuckDbValue::Int64(0),
                        DuckDbValue::Utf8("age".into()),
                        DuckDbValue::Utf8("int64".into()),
                        DuckDbValue::Int64(3),
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                    ],
                    vec![
                        DuckDbValue::Int64(5),
                        DuckDbValue::Int64(1),
                        DuckDbValue::Int64(2),
                        DuckDbValue::Int64(1),
                        DuckDbValue::Utf8("city".into()),
                        DuckDbValue::Utf8("varchar".into()),
                        DuckDbValue::Int64(3),
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                    ],
                    vec![
                        DuckDbValue::Int64(5),
                        DuckDbValue::Int64(2),
                        DuckDbValue::Null,
                        DuckDbValue::Int64(1),
                        DuckDbValue::Utf8("city".into()),
                        DuckDbValue::Utf8("varchar".into()),
                        DuckDbValue::Int64(3),
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                        DuckDbValue::Null,
                    ],
                    vec![
                        DuckDbValue::Int64(10),
                        DuckDbValue::Int64(2),
                        DuckDbValue::Null,
                        DuckDbValue::Int64(2),
                        DuckDbValue::Utf8("score".into()),
                        DuckDbValue::Utf8("int32".into()),
                        DuckDbValue::Int64(3),
                        DuckDbValue::Utf8("NULL".into()),
                        DuckDbValue::Utf8("literal".into()),
                        DuckDbValue::Utf8("duckdb".into()),
                    ],
                ]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT snapshot_id, schema_version, next_catalog_id, next_file_id \
                         FROM ducklake_snapshot ORDER BY snapshot_id DESC LIMIT 1"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(2),
                    DuckDbValue::Int64(2),
                    DuckDbValue::Int64(11),
                    DuckDbValue::Int64(20),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT changes_made FROM ducklake_snapshot_changes ORDER BY snapshot_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Utf8("altered_table:1".into())]]
            );
            catalog.disconnect().await.unwrap();
        });
}

#[test]
fn live_quack_server_roundtrip_when_enabled() {
    let live_enabled = std::env::var("DUCKLAKE_QUACK_LIVE_TEST")
        .ok()
        .is_some_and(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "on"));
    if !live_enabled {
        eprintln!(
            "skipping live Quack server test; set DUCKLAKE_QUACK_LIVE_TEST=1 and run scripts/start-quack-server.sh"
        );
        return;
    }
    let _live_guard = QUACK_LIVE_TEST_LOCK.lock().unwrap();

    let uri =
        std::env::var("DUCKLAKE_QUACK_TEST_URI").unwrap_or_else(|_| "quack:localhost".to_string());
    let token = std::env::var("DUCKLAKE_QUACK_TEST_TOKEN").unwrap_or_else(|_| "asdf".to_string());
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
            let catalog = QuackCatalog::try_new(uri, token).unwrap();
            let result = catalog
                .query_sql(
                    "SELECT * FROM (VALUES (42::INTEGER, 'alpha'), (7::INTEGER, 'beta')) \
                     AS t(answer, label) ORDER BY answer",
                )
                .await
                .unwrap();

            assert_eq!(result.column_names, vec!["answer", "label"]);
            assert_eq!(result.column_types, vec!["INTEGER", "VARCHAR"]);
            assert_eq!(
                result.rows,
                vec![
                    vec![DuckDbValue::Int32(7), DuckDbValue::Utf8("beta".into())],
                    vec![DuckDbValue::Int32(42), DuckDbValue::Utf8("alpha".into())],
                ]
            );

            let special = catalog
                .query_sql(
                    "SELECT \
                        123.45::DECIMAL(20,2) AS amount, \
                        -9223372036854775809::HUGEINT AS signed_value, \
                        18446744073709551620::UHUGEINT AS unsigned_value, \
                        '00010203-0405-0607-0809-0a0b0c0d0e0f'::UUID AS id, \
                        INTERVAL '14 months 3 days 4005 microseconds' AS duration, \
                        TIMETZ '12:34:56.123+01' AS wall_clock",
                )
                .await
                .unwrap();

            assert_eq!(
                special.column_names,
                vec![
                    "amount",
                    "signed_value",
                    "unsigned_value",
                    "id",
                    "duration",
                    "wall_clock",
                ]
            );
            assert_eq!(
                special.rows,
                vec![vec![
                    DuckDbValue::Decimal {
                        value: 12345,
                        width: 20,
                        scale: 2,
                    },
                    DuckDbValue::Int128(-9_223_372_036_854_775_809),
                    DuckDbValue::UInt128(18_446_744_073_709_551_620),
                    DuckDbValue::Uuid([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
                    DuckDbValue::Interval {
                        months: 14,
                        days: 3,
                        micros: 4_005,
                    },
                    DuckDbValue::TimeTzMicros(encode_duckdb_timetz(45_296_123_000, 3_600)),
                ]]
            );

            let mutation = catalog
                .query_sql("CREATE TEMP TABLE ducklake_quack_client_mutation_test(answer INTEGER)")
                .await
                .unwrap();
            assert!(mutation.rows.is_empty());
            catalog
                .query_sql("INSERT INTO ducklake_quack_client_mutation_test VALUES (1), (2), (3)")
                .await
                .unwrap();
            catalog
                .query_sql("DELETE FROM ducklake_quack_client_mutation_test WHERE answer = 2")
                .await
                .unwrap();
            let mutation_rows = catalog
                .query_sql("SELECT answer FROM ducklake_quack_client_mutation_test ORDER BY answer")
                .await
                .unwrap();
            assert_eq!(
                mutation_rows.rows,
                vec![vec![DuckDbValue::Int32(1)], vec![DuckDbValue::Int32(3)]]
            );
            catalog
                .query_sql("DROP TABLE ducklake_quack_client_mutation_test")
                .await
                .unwrap();

            for table in [
                "ducklake_table_stats",
                "ducklake_table_column_stats",
                "ducklake_file_partition_value",
                "ducklake_file_column_stats",
                "ducklake_delete_file",
                "ducklake_data_file",
                "ducklake_column",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot( \
                         snapshot_id BIGINT, snapshot_time TIMESTAMP, schema_version BIGINT, \
                         next_catalog_id BIGINT, next_file_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot_changes( \
                         snapshot_id BIGINT, changes_made VARCHAR, author VARCHAR, \
                         commit_message VARCHAR, commit_extra_info VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_data_file( \
                         data_file_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, file_order BIGINT, path VARCHAR, \
                         path_is_relative BOOLEAN, file_format VARCHAR, record_count BIGINT, \
                         file_size_bytes BIGINT, footer_size BIGINT, row_id_start BIGINT, \
                         partition_id BIGINT, encryption_key VARCHAR, mapping_id BIGINT, \
                         partial_max BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_delete_file( \
                         delete_file_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, data_file_id BIGINT, path VARCHAR, \
                         path_is_relative BOOLEAN, format VARCHAR, delete_count BIGINT, \
                         file_size_bytes BIGINT, footer_size BIGINT, encryption_key VARCHAR, \
                         partial_max BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_file_column_stats( \
                         data_file_id BIGINT, table_id BIGINT, column_id BIGINT, \
                         column_size_bytes BIGINT, value_count BIGINT, null_count BIGINT, \
                         min_value VARCHAR, max_value VARCHAR, contains_nan BOOLEAN, \
                         extra_stats VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_column( \
                         column_id BIGINT, begin_snapshot BIGINT, end_snapshot BIGINT, \
                         table_id BIGINT, column_order BIGINT, column_name VARCHAR, \
                         column_type VARCHAR, initial_default VARCHAR, default_value VARCHAR, \
                         nulls_allowed BOOLEAN, parent_column BIGINT, default_value_type VARCHAR, \
                         default_value_dialect VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_table_column_stats( \
                         table_id BIGINT, column_id BIGINT, contains_null BOOLEAN, \
                         contains_nan BOOLEAN, min_value VARCHAR, max_value VARCHAR, \
                         extra_stats VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_file_partition_value( \
                         data_file_id BIGINT, table_id BIGINT, partition_key_index BIGINT, \
                         partition_value VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_table_stats( \
                         table_id BIGINT, record_count BIGINT, next_row_id BIGINT, \
                         file_size_bytes BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_snapshot VALUES \
                     (1, TIMESTAMP '2024-01-01 00:00:00', 1, 1, 10)",
                )
                .await
                .unwrap();
            catalog
                .query_sql("INSERT INTO ducklake_table_stats VALUES (1, 0, 0, 0)")
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_column VALUES \
                     (2, 1, NULL, 1, 1, 'value', 'int64', NULL, NULL, FALSE, NULL, NULL, NULL)",
                )
                .await
                .unwrap();

            let append_snapshot = catalog
                .commit_append_files(vec![quack_append_file(10, SnapshotId(1))])
                .await
                .unwrap();
            assert_eq!(append_snapshot, SnapshotId(2));
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT snapshot_id, next_file_id \
                         FROM ducklake_snapshot ORDER BY snapshot_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![DuckDbValue::Int64(1), DuckDbValue::Int64(10)],
                    vec![DuckDbValue::Int64(2), DuckDbValue::Int64(11)],
                ]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT data_file_id, begin_snapshot, file_order, path, partition_id \
                         FROM ducklake_data_file"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(10),
                    DuckDbValue::Int64(2),
                    DuckDbValue::Int64(10),
                    DuckDbValue::Utf8("quack-append-10.parquet".into()),
                    DuckDbValue::Int64(3),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT column_id, min_value, max_value, contains_nan \
                         FROM ducklake_file_column_stats"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(2),
                    DuckDbValue::Utf8("10".into()),
                    DuckDbValue::Utf8("20".into()),
                    DuckDbValue::Boolean(false),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT column_id, contains_null, contains_nan, min_value, max_value \
                         FROM ducklake_table_column_stats ORDER BY column_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(2),
                    DuckDbValue::Boolean(false),
                    DuckDbValue::Boolean(false),
                    DuckDbValue::Utf8("10".into()),
                    DuckDbValue::Utf8("20".into()),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT partition_key_index, partition_value \
                         FROM ducklake_file_partition_value"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(0), DuckDbValue::Utf8("4".into())]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT record_count, next_row_id, file_size_bytes \
                         FROM ducklake_table_stats WHERE table_id = 1"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(2),
                    DuckDbValue::Int64(2),
                    DuckDbValue::Int64(25),
                ]]
            );
            assert!(
                catalog
                    .commit_append_files(vec![quack_append_file(11, SnapshotId(1))])
                    .await
                    .unwrap_err()
                    .is_conflict()
            );
            let delete_snapshot = catalog
                .commit_delete_files(vec![quack_delete_file(11, SnapshotId(2), 10, 1, None)])
                .await
                .unwrap();
            assert_eq!(delete_snapshot, SnapshotId(3));
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT delete_file_id, begin_snapshot, end_snapshot, data_file_id, path, format, footer_size \
                         FROM ducklake_delete_file ORDER BY delete_file_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(11),
                    DuckDbValue::Int64(3),
                    DuckDbValue::Null,
                    DuckDbValue::Int64(10),
                    DuckDbValue::Utf8("quack-delete-11.parquet".into()),
                    DuckDbValue::Utf8("parquet".into()),
                    DuckDbValue::Int64(4),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT snapshot_id, next_file_id \
                         FROM ducklake_snapshot ORDER BY snapshot_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![DuckDbValue::Int64(1), DuckDbValue::Int64(10)],
                    vec![DuckDbValue::Int64(2), DuckDbValue::Int64(11)],
                    vec![DuckDbValue::Int64(3), DuckDbValue::Int64(12)],
                ]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT record_count, next_row_id, file_size_bytes \
                         FROM ducklake_table_stats WHERE table_id = 1"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(1),
                    DuckDbValue::Int64(2),
                    DuckDbValue::Int64(25),
                ]]
            );
            assert!(
                catalog
                    .commit_delete_files(vec![quack_delete_file(12, SnapshotId(2), 10, 1, None)])
                    .await
                    .unwrap_err()
                    .is_conflict()
            );
            let replacement_snapshot = catalog
                .commit_delete_files(vec![quack_delete_file(
                    12,
                    SnapshotId(3),
                    10,
                    1,
                    Some(DeleteFileId(11)),
                )])
                .await
                .unwrap();
            assert_eq!(replacement_snapshot, SnapshotId(4));
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT delete_file_id, begin_snapshot, end_snapshot \
                         FROM ducklake_delete_file ORDER BY delete_file_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![
                        DuckDbValue::Int64(11),
                        DuckDbValue::Int64(3),
                        DuckDbValue::Int64(4)
                    ],
                    vec![DuckDbValue::Int64(12), DuckDbValue::Int64(4), DuckDbValue::Null],
                ]
            );
            let mut table_change_append = quack_append_file(13, SnapshotId(4));
            table_change_append.column_stats[0].min_value = Some("5".into());
            table_change_append.column_stats[0].max_value = Some("30".into());
            let table_change_snapshot = catalog
                .commit_table_changes(TableChangeCommit {
                    table_id: TableId(1),
                    expected_snapshot: SnapshotId(4),
                    append_files: vec![table_change_append],
                    delete_files: vec![quack_delete_file(
                        14,
                        SnapshotId(4),
                        10,
                        1,
                        Some(DeleteFileId(12)),
                    )],
                    inlined_file_deletions: vec![quack_inlined_file_deletion_commit(
                        13,
                        SnapshotId(4),
                        vec![0],
                    )],
                    inlined_data_appends: Vec::new(),
                    inlined_data_deletions: Vec::new(),
                    flushed_inlined_data: Vec::new(),
                    flushed_inlined_file_deletions: Vec::new(),
                    changes_made: "updated_table:1".into(),
                    commit_message: "datafusion update".into(),
                })
                .await
                .unwrap();
            assert_eq!(table_change_snapshot, SnapshotId(5));
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT snapshot_id, next_file_id \
                         FROM ducklake_snapshot ORDER BY snapshot_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![DuckDbValue::Int64(1), DuckDbValue::Int64(10)],
                    vec![DuckDbValue::Int64(2), DuckDbValue::Int64(11)],
                    vec![DuckDbValue::Int64(3), DuckDbValue::Int64(12)],
                    vec![DuckDbValue::Int64(4), DuckDbValue::Int64(13)],
                    vec![DuckDbValue::Int64(5), DuckDbValue::Int64(15)],
                ]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT delete_file_id, begin_snapshot, end_snapshot \
                         FROM ducklake_delete_file ORDER BY delete_file_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![
                        DuckDbValue::Int64(11),
                        DuckDbValue::Int64(3),
                        DuckDbValue::Int64(4)
                    ],
                    vec![
                        DuckDbValue::Int64(12),
                        DuckDbValue::Int64(4),
                        DuckDbValue::Int64(5)
                    ],
                    vec![DuckDbValue::Int64(14), DuckDbValue::Int64(5), DuckDbValue::Null],
                ]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT file_id, row_id, begin_snapshot \
                         FROM ducklake_inlined_delete_1 ORDER BY file_id, row_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(13),
                    DuckDbValue::Int64(0),
                    DuckDbValue::Int64(5),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT changes_made, commit_message \
                         FROM ducklake_snapshot_changes WHERE snapshot_id = 5"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Utf8("updated_table:1".into()),
                    DuckDbValue::Utf8("datafusion update".into()),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT record_count, next_row_id, file_size_bytes \
                         FROM ducklake_table_stats WHERE table_id = 1"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(0),
                    DuckDbValue::Int64(4),
                    DuckDbValue::Int64(50),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT column_id, contains_null, contains_nan, min_value, max_value \
                         FROM ducklake_table_column_stats ORDER BY column_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(2),
                    DuckDbValue::Boolean(false),
                    DuckDbValue::Boolean(false),
                    DuckDbValue::Utf8("5".into()),
                    DuckDbValue::Utf8("30".into()),
                ]]
            );
            assert!(
                catalog
                    .commit_table_changes(TableChangeCommit {
                        table_id: TableId(1),
                        expected_snapshot: SnapshotId(4),
                        append_files: vec![quack_append_file(15, SnapshotId(4))],
                        delete_files: Vec::new(),
                        inlined_file_deletions: Vec::new(),
                        inlined_data_appends: Vec::new(),
                        inlined_data_deletions: Vec::new(),
                        flushed_inlined_data: Vec::new(),
                        flushed_inlined_file_deletions: Vec::new(),
                        changes_made: "updated_table:1".into(),
                        commit_message: "datafusion update".into(),
                    })
                    .await
                    .unwrap_err()
                    .is_conflict()
            );
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_inlined_data_1_1( \
                         row_id BIGINT, begin_snapshot BIGINT, end_snapshot BIGINT, \
                         value BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_inlined_data_1_1 VALUES \
                     (4, 5, NULL, 40), (5, 5, NULL, 50), (6, 4, 5, 60)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "UPDATE ducklake_table_stats \
                     SET record_count = 2, next_row_id = 6 \
                     WHERE table_id = 1",
                )
                .await
                .unwrap();
            let mut flushed_file = quack_append_file(15, SnapshotId(5));
            flushed_file.begin_snapshot = Some(SnapshotId(5));
            flushed_file.row_id_start = Some(4);
            flushed_file.explicit_row_ids = true;
            flushed_file.record_count = 2;
            flushed_file.file_size_bytes = 30;
            flushed_file.partition_id = None;
            flushed_file.partition_values.clear();
            flushed_file.partial_max = None;
            flushed_file.column_stats.clear();
            let flush_snapshot = catalog
                .commit_table_changes(TableChangeCommit {
                    table_id: TableId(1),
                    expected_snapshot: SnapshotId(5),
                    append_files: vec![flushed_file],
                    delete_files: Vec::new(),
                    inlined_file_deletions: Vec::new(),
                    inlined_data_appends: Vec::new(),
                    inlined_data_deletions: Vec::new(),
                    flushed_inlined_data: vec![FlushedInlinedDataCommit {
                        table_name: "ducklake_inlined_data_1_1".into(),
                        row_ids: vec![4, 5],
                        rows_flushed: 2,
                    }],
                    flushed_inlined_file_deletions: Vec::new(),
                    changes_made: "inline_flush:1".into(),
                    commit_message: "datafusion flush inlined data".into(),
                })
                .await
                .unwrap();
            assert_eq!(flush_snapshot, SnapshotId(6));
            assert_eq!(
                catalog
                    .query_sql("SELECT row_id FROM ducklake_inlined_data_1_1 ORDER BY row_id")
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(6)]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT begin_snapshot, partial_max, row_id_start, record_count \
                         FROM ducklake_data_file WHERE data_file_id = 15"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(5),
                    DuckDbValue::Null,
                    DuckDbValue::Int64(4),
                    DuckDbValue::Int64(2),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT record_count, next_row_id, file_size_bytes \
                         FROM ducklake_table_stats WHERE table_id = 1"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(2),
                    DuckDbValue::Int64(6),
                    DuckDbValue::Int64(80),
                ]]
            );
            let mut flushed_delete = quack_delete_file(16, SnapshotId(6), 13, 0, None);
            flushed_delete.begin_snapshot = Some(SnapshotId(5));
            flushed_delete.delete_count = 1;
            flushed_delete.rows_deleted = 0;
            flushed_delete.partial_max = Some(SnapshotId(5));
            let flush_delete_snapshot = catalog
                .commit_table_changes(TableChangeCommit {
                    table_id: TableId(1),
                    expected_snapshot: SnapshotId(6),
                    append_files: Vec::new(),
                    delete_files: vec![flushed_delete],
                    inlined_file_deletions: Vec::new(),
                    inlined_data_appends: Vec::new(),
                    inlined_data_deletions: Vec::new(),
                    flushed_inlined_data: Vec::new(),
                    flushed_inlined_file_deletions: vec![FlushedInlinedFileDeletionCommit {
                        rows: vec![crate::FlushedInlinedFileDeletion {
                            data_file_id: DataFileId(13),
                            row_id: 0,
                            begin_snapshot: SnapshotId(5),
                        }],
                    }],
                    changes_made: "inline_flush:1".into(),
                    commit_message: "datafusion flush inlined deletes".into(),
                })
                .await
                .unwrap();
            assert_eq!(flush_delete_snapshot, SnapshotId(7));
            assert_eq!(
                catalog
                    .query_sql("SELECT COUNT(*) FROM ducklake_inlined_delete_1")
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(0)]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT begin_snapshot, partial_max, delete_count \
                         FROM ducklake_delete_file WHERE delete_file_id = 16"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(5),
                    DuckDbValue::Int64(5),
                    DuckDbValue::Int64(1),
                ]]
            );
            let truncate_snapshot = catalog
                .commit_truncate_table(TruncateTableCommit {
                    table_id: TableId(1),
                    expected_snapshot: SnapshotId(7),
                    rows_deleted: 0,
                    inlined_data_deletions: Vec::new(),
                    changes_made: "deleted_from_table:1".into(),
                    commit_message: "datafusion truncate".into(),
                })
                .await
                .unwrap();
            assert_eq!(truncate_snapshot, SnapshotId(8));
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT snapshot_id, next_file_id \
                         FROM ducklake_snapshot ORDER BY snapshot_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![DuckDbValue::Int64(1), DuckDbValue::Int64(10)],
                    vec![DuckDbValue::Int64(2), DuckDbValue::Int64(11)],
                    vec![DuckDbValue::Int64(3), DuckDbValue::Int64(12)],
                    vec![DuckDbValue::Int64(4), DuckDbValue::Int64(13)],
                    vec![DuckDbValue::Int64(5), DuckDbValue::Int64(15)],
                    vec![DuckDbValue::Int64(6), DuckDbValue::Int64(16)],
                    vec![DuckDbValue::Int64(7), DuckDbValue::Int64(17)],
                    vec![DuckDbValue::Int64(8), DuckDbValue::Int64(17)],
                ]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT data_file_id, end_snapshot \
                         FROM ducklake_data_file ORDER BY data_file_id"
                    )
                    .await
                    .unwrap()
                .rows,
                vec![
                    vec![DuckDbValue::Int64(10), DuckDbValue::Int64(8)],
                    vec![DuckDbValue::Int64(13), DuckDbValue::Int64(8)],
                    vec![DuckDbValue::Int64(15), DuckDbValue::Int64(8)],
                ]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT delete_file_id, end_snapshot \
                         FROM ducklake_delete_file ORDER BY delete_file_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![
                    vec![
                        DuckDbValue::Int64(11),
                        DuckDbValue::Int64(4)
                    ],
                    vec![
                        DuckDbValue::Int64(12),
                        DuckDbValue::Int64(5)
                    ],
                    vec![DuckDbValue::Int64(14), DuckDbValue::Int64(8)],
                    vec![DuckDbValue::Int64(16), DuckDbValue::Int64(8)],
                ]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT record_count, next_row_id, file_size_bytes \
                         FROM ducklake_table_stats WHERE table_id = 1"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(0),
                    DuckDbValue::Int64(6),
                    DuckDbValue::Int64(0),
                ]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT changes_made, commit_message \
                         FROM ducklake_snapshot_changes WHERE snapshot_id = 8"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Utf8("deleted_from_table:1".into()),
                    DuckDbValue::Utf8("datafusion truncate".into()),
                ]]
            );
            assert!(
                catalog
                    .commit_truncate_table(TruncateTableCommit {
                        table_id: TableId(1),
                        expected_snapshot: SnapshotId(7),
                        rows_deleted: 0,
                        inlined_data_deletions: Vec::new(),
                        changes_made: "deleted_from_table:1".into(),
                        commit_message: "datafusion truncate".into(),
                    })
                    .await
                    .unwrap_err()
                    .is_conflict()
            );

            for table in [
                "ducklake_metadata",
                "ducklake_schema",
                "ducklake_table",
                "ducklake_inlined_delete_1",
                "ducklake_table_stats",
                "ducklake_table_column_stats",
                "ducklake_file_partition_value",
                "ducklake_file_column_stats",
                "ducklake_delete_file",
                "ducklake_files_scheduled_for_deletion",
                "ducklake_data_file",
                "ducklake_column",
                "ducklake_snapshot_changes",
                "ducklake_snapshot",
            ] {
                catalog
                    .query_sql(format!("DROP TABLE IF EXISTS {table}"))
                    .await
                    .unwrap();
            }
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_metadata( \
                         key VARCHAR, value VARCHAR, scope VARCHAR, scope_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_schema( \
                         schema_id BIGINT, schema_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_name VARCHAR, path VARCHAR, \
                         path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_table( \
                         table_id BIGINT, table_uuid UUID, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, schema_id BIGINT, table_name VARCHAR, \
                         path VARCHAR, path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot( \
                         snapshot_id BIGINT, snapshot_time TIMESTAMP, schema_version BIGINT, \
                         next_catalog_id BIGINT, next_file_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_snapshot_changes( \
                         snapshot_id BIGINT, changes_made VARCHAR, author VARCHAR, \
                         commit_message VARCHAR, commit_extra_info VARCHAR \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_data_file( \
                         data_file_id BIGINT, table_id BIGINT, begin_snapshot BIGINT, \
                         end_snapshot BIGINT, path VARCHAR, path_is_relative BOOLEAN \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_files_scheduled_for_deletion( \
                         data_file_id BIGINT, path VARCHAR, path_is_relative BOOLEAN, \
                         schedule_start TIMESTAMP \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_metadata VALUES \
                     ('version', '1.0', NULL, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_schema VALUES \
                     (1, '00000000-0000-0000-0000-000000000001', 1, NULL, 'main', NULL, FALSE)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_table VALUES \
                     (1, '00000000-0000-0000-0000-000000000002', 1, NULL, 1, 'roundtrip', NULL, FALSE)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_snapshot VALUES \
                     (1, TIMESTAMP '2024-01-01 00:00:00', 1, 1, 10), \
                     (2, TIMESTAMP '2024-01-02 00:00:00', 1, 1, 20), \
                     (3, TIMESTAMP '2024-01-03 00:00:00', 1, 1, 30)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_snapshot_changes VALUES (1, 'insert', NULL, NULL, NULL)",
                )
                .await
                .unwrap();
            catalog
                .query_sql(
                    "INSERT INTO ducklake_data_file VALUES \
                     (10, 1, 1, 2, 'obsolete.parquet', TRUE), \
                     (20, 1, 2, NULL, 'live.parquet', TRUE)",
                )
                .await
                .unwrap();

            let expired = catalog
                .expire_snapshots(ExpireSnapshotsOptions::versions(vec![SnapshotId(1)]))
                .await
                .unwrap();
            assert_eq!(
                expired
                    .iter()
                    .map(|snapshot| snapshot.snapshot_id)
                    .collect::<Vec<_>>(),
                vec![SnapshotId(1)]
            );
            assert_eq!(
                catalog
                    .query_sql("SELECT snapshot_id FROM ducklake_snapshot ORDER BY snapshot_id")
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(2)], vec![DuckDbValue::Int64(3)]]
            );
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT data_file_id, path FROM ducklake_files_scheduled_for_deletion"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![
                    DuckDbValue::Int64(10),
                    DuckDbValue::Utf8("obsolete.parquet".into())
                ]]
            );

            let cleanup_temp = tempfile::tempdir().unwrap();
            let old_path = cleanup_temp.path().join("old.parquet");
            let future_path = cleanup_temp.path().join("future.parquet");
            let absolute_path = cleanup_temp.path().join("absolute.parquet");
            fs::write(&old_path, b"old").unwrap();
            fs::write(&future_path, b"future").unwrap();
            fs::write(&absolute_path, b"absolute").unwrap();

            catalog
                .query_sql("DROP TABLE IF EXISTS ducklake_metadata")
                .await
                .unwrap();
            catalog
                .query_sql(
                    "CREATE TEMP TABLE ducklake_metadata( \
                         key VARCHAR, value VARCHAR, scope VARCHAR, scope_id BIGINT \
                     )",
                )
                .await
                .unwrap();
            catalog
                .query_sql(format!(
                    "INSERT INTO ducklake_metadata VALUES \
                     ('version', '1.0', NULL, NULL), \
                     ('data_path', {}, NULL, NULL)",
                    sql_string_literal(&cleanup_temp.path().to_string_lossy())
                ))
                .await
                .unwrap();
            catalog
                .query_sql("DELETE FROM ducklake_files_scheduled_for_deletion")
                .await
                .unwrap();
            catalog
                .query_sql(format!(
                    "INSERT INTO ducklake_files_scheduled_for_deletion VALUES \
                     (1, 'old.parquet', TRUE, TIMESTAMP '2026-01-01 00:00:00'), \
                     (2, 'future.parquet', TRUE, TIMESTAMP '2026-01-03 00:00:00'), \
                     (3, {}, FALSE, TIMESTAMP '2026-01-01 00:00:00'), \
                     (4, 'already-gone.parquet', TRUE, TIMESTAMP '2026-01-01 00:00:00')",
                    sql_string_literal(&absolute_path.to_string_lossy())
                ))
                .await
                .unwrap();

            let dry_run = catalog
                .cleanup_old_files(CleanupOldFilesOptions::dry_run_all())
                .await
                .unwrap();
            assert_eq!(
                dry_run
                    .iter()
                    .map(|file| file.data_file_id)
                    .collect::<Vec<_>>(),
                vec![DataFileId(1), DataFileId(3), DataFileId(4), DataFileId(2)]
            );
            assert!(old_path.exists());
            assert!(future_path.exists());
            assert!(absolute_path.exists());

            let cleaned = catalog
                .cleanup_old_files(CleanupOldFilesOptions::older_than(
                    1_767_312_000_000_000,
                ))
                .await
                .unwrap();
            assert_eq!(
                cleaned
                    .iter()
                    .map(|file| file.data_file_id)
                    .collect::<Vec<_>>(),
                vec![DataFileId(1), DataFileId(3), DataFileId(4)]
            );
            assert!(!old_path.exists());
            assert!(!absolute_path.exists());
            assert!(future_path.exists());
            assert_eq!(
                catalog
                    .query_sql(
                        "SELECT data_file_id FROM ducklake_files_scheduled_for_deletion \
                         ORDER BY data_file_id"
                    )
                    .await
                    .unwrap()
                    .rows,
                vec![vec![DuckDbValue::Int64(2)]]
            );
            catalog.disconnect().await.unwrap();
        });
}

#[test]
fn validates_token_length_like_server_reference() {
    assert!(validate_token("abcd").is_ok());
    assert!(format!("{}", validate_token("abc").unwrap_err()).contains("at least 4"));
}
