use citadel::{Argon2Profile, DatabaseBuilder};
use citadel_sql::{Connection, ExecutionResult, QueryResult, Value};

fn create_db(dir: &std::path::Path) -> citadel::Database {
    let db_path = dir.join("test.db");
    DatabaseBuilder::new(db_path)
        .passphrase(b"test-passphrase")
        .argon2_profile(Argon2Profile::Iot)
        .create()
        .unwrap()
}

fn assert_ok(result: ExecutionResult) {
    match result {
        ExecutionResult::Ok => {}
        other => panic!("expected Ok, got {other:?}"),
    }
}

fn assert_rows_affected(result: ExecutionResult, expected: u64) {
    match result {
        ExecutionResult::RowsAffected(n) => assert_eq!(n, expected),
        other => panic!("expected RowsAffected({expected}), got {other:?}"),
    }
}

fn query(conn: &Connection, sql: &str) -> QueryResult {
    match conn.execute(sql).unwrap() {
        ExecutionResult::Query(qr) => qr,
        other => panic!("expected Query, got {other:?}"),
    }
}

fn setup_tables(conn: &Connection) {
    assert_ok(
        conn.execute("CREATE TABLE t1 (id INTEGER NOT NULL PRIMARY KEY, val INTEGER)")
            .unwrap(),
    );
    assert_ok(
        conn.execute("CREATE TABLE t2 (id INTEGER NOT NULL PRIMARY KEY, val INTEGER)")
            .unwrap(),
    );
    assert_rows_affected(
        conn.execute("INSERT INTO t1 (id, val) VALUES (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)")
            .unwrap(),
        5,
    );
    assert_rows_affected(
        conn.execute("INSERT INTO t2 (id, val) VALUES (2, 200), (4, 400), (6, 600)")
            .unwrap(),
        3,
    );
}

fn get_ids(qr: &QueryResult) -> Vec<i64> {
    let mut ids: Vec<i64> = qr
        .rows
        .iter()
        .map(|r| match &r[0] {
            Value::Integer(i) => *i,
            other => panic!("expected int, got {other:?}"),
        })
        .collect();
    ids.sort();
    ids
}

#[test]
fn in_subquery_all_match() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(
        conn.execute("CREATE TABLE a (id INTEGER NOT NULL PRIMARY KEY)")
            .unwrap(),
    );
    assert_ok(
        conn.execute("CREATE TABLE b (id INTEGER NOT NULL PRIMARY KEY)")
            .unwrap(),
    );
    assert_rows_affected(
        conn.execute("INSERT INTO a (id) VALUES (1), (2), (3)")
            .unwrap(),
        3,
    );
    assert_rows_affected(
        conn.execute("INSERT INTO b (id) VALUES (1), (2), (3)")
            .unwrap(),
        3,
    );

    let qr = query(&conn, "SELECT id FROM a WHERE id IN (SELECT id FROM b)");
    assert_eq!(get_ids(&qr), vec![1, 2, 3]);
}

#[test]
fn in_subquery_no_match() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(
        conn.execute("CREATE TABLE a (id INTEGER NOT NULL PRIMARY KEY)")
            .unwrap(),
    );
    assert_ok(
        conn.execute("CREATE TABLE b (id INTEGER NOT NULL PRIMARY KEY)")
            .unwrap(),
    );
    assert_rows_affected(
        conn.execute("INSERT INTO a (id) VALUES (1), (2), (3)")
            .unwrap(),
        3,
    );
    assert_rows_affected(
        conn.execute("INSERT INTO b (id) VALUES (4), (5), (6)")
            .unwrap(),
        3,
    );

    let qr = query(&conn, "SELECT id FROM a WHERE id IN (SELECT id FROM b)");
    assert_eq!(qr.rows.len(), 0);
}

#[test]
fn in_subquery_both_tables_empty() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(
        conn.execute("CREATE TABLE a (id INTEGER NOT NULL PRIMARY KEY)")
            .unwrap(),
    );
    assert_ok(
        conn.execute("CREATE TABLE b (id INTEGER NOT NULL PRIMARY KEY)")
            .unwrap(),
    );

    let qr = query(&conn, "SELECT id FROM a WHERE id IN (SELECT id FROM b)");
    assert_eq!(qr.rows.len(), 0);
}

#[test]
fn in_subquery_outer_empty() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(
        conn.execute("CREATE TABLE a (id INTEGER NOT NULL PRIMARY KEY)")
            .unwrap(),
    );
    assert_ok(
        conn.execute("CREATE TABLE b (id INTEGER NOT NULL PRIMARY KEY)")
            .unwrap(),
    );
    assert_rows_affected(conn.execute("INSERT INTO b (id) VALUES (1)").unwrap(), 1);

    let qr = query(&conn, "SELECT id FROM a WHERE id IN (SELECT id FROM b)");
    assert_eq!(qr.rows.len(), 0);
}

#[test]
fn in_subquery_inner_empty() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(
        conn.execute("CREATE TABLE a (id INTEGER NOT NULL PRIMARY KEY)")
            .unwrap(),
    );
    assert_ok(
        conn.execute("CREATE TABLE b (id INTEGER NOT NULL PRIMARY KEY)")
            .unwrap(),
    );
    assert_rows_affected(
        conn.execute("INSERT INTO a (id) VALUES (1), (2)").unwrap(),
        2,
    );

    let qr = query(&conn, "SELECT id FROM a WHERE id IN (SELECT id FROM b)");
    assert_eq!(qr.rows.len(), 0);
}

#[test]
fn in_subquery_duplicate_values_in_subquery() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(
        conn.execute("CREATE TABLE a (id INTEGER NOT NULL PRIMARY KEY)")
            .unwrap(),
    );
    assert_ok(
        conn.execute("CREATE TABLE b (id INTEGER NOT NULL PRIMARY KEY, ref_id INTEGER NOT NULL)")
            .unwrap(),
    );
    assert_rows_affected(
        conn.execute("INSERT INTO a (id) VALUES (1), (2), (3)")
            .unwrap(),
        3,
    );
    assert_rows_affected(
        conn.execute("INSERT INTO b (id, ref_id) VALUES (1, 1), (2, 1), (3, 2), (4, 2)")
            .unwrap(),
        4,
    );

    let qr = query(&conn, "SELECT id FROM a WHERE id IN (SELECT ref_id FROM b)");
    assert_eq!(get_ids(&qr), vec![1, 2]);
}

#[test]
fn in_subquery_with_text_values() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(
        conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, name TEXT NOT NULL)")
            .unwrap(),
    );
    assert_ok(
        conn.execute("CREATE TABLE allowed (id INTEGER NOT NULL PRIMARY KEY, name TEXT NOT NULL)")
            .unwrap(),
    );
    assert_rows_affected(
        conn.execute("INSERT INTO t (id, name) VALUES (1, 'Alice'), (2, 'Bob'), (3, 'Charlie')")
            .unwrap(),
        3,
    );
    assert_rows_affected(
        conn.execute("INSERT INTO allowed (id, name) VALUES (1, 'Alice'), (2, 'Charlie')")
            .unwrap(),
        2,
    );

    let qr = query(
        &conn,
        "SELECT id FROM t WHERE name IN (SELECT name FROM allowed)",
    );
    assert_eq!(get_ids(&qr), vec![1, 3]);
}

#[test]
fn three_valued_in_true() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(
        conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER NOT NULL)")
            .unwrap(),
    );
    assert_rows_affected(
        conn.execute("INSERT INTO t (id, val) VALUES (1, 10)")
            .unwrap(),
        1,
    );

    let qr = query(&conn, "SELECT val FROM t WHERE val IN (10, 20)");
    assert_eq!(qr.rows.len(), 1);
    assert_eq!(qr.rows[0][0], Value::Integer(10));
}

#[test]
fn three_valued_in_false() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(
        conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER NOT NULL)")
            .unwrap(),
    );
    assert_rows_affected(
        conn.execute("INSERT INTO t (id, val) VALUES (1, 10)")
            .unwrap(),
        1,
    );

    let qr = query(&conn, "SELECT val FROM t WHERE val IN (20, 30)");
    assert_eq!(qr.rows.len(), 0);
}

#[test]
fn three_valued_in_null_result() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(
        conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER NOT NULL)")
            .unwrap(),
    );
    assert_rows_affected(
        conn.execute("INSERT INTO t (id, val) VALUES (1, 10)")
            .unwrap(),
        1,
    );

    let qr = query(&conn, "SELECT val FROM t WHERE val IN (20, NULL)");
    assert_eq!(qr.rows.len(), 0);
}

#[test]
fn three_valued_not_in_true() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(
        conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER NOT NULL)")
            .unwrap(),
    );
    assert_rows_affected(
        conn.execute("INSERT INTO t (id, val) VALUES (1, 10)")
            .unwrap(),
        1,
    );

    let qr = query(&conn, "SELECT val FROM t WHERE val NOT IN (20, 30)");
    assert_eq!(qr.rows.len(), 1);
}

#[test]
fn three_valued_not_in_false() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(
        conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER NOT NULL)")
            .unwrap(),
    );
    assert_rows_affected(
        conn.execute("INSERT INTO t (id, val) VALUES (1, 10)")
            .unwrap(),
        1,
    );

    let qr = query(&conn, "SELECT val FROM t WHERE val NOT IN (10, 20)");
    assert_eq!(qr.rows.len(), 0);
}

#[test]
fn three_valued_not_in_null_result() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(
        conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER NOT NULL)")
            .unwrap(),
    );
    assert_rows_affected(
        conn.execute("INSERT INTO t (id, val) VALUES (1, 10)")
            .unwrap(),
        1,
    );

    let qr = query(&conn, "SELECT val FROM t WHERE val NOT IN (20, NULL)");
    assert_eq!(qr.rows.len(), 0);
}

#[test]
fn null_lhs_in_list_always_null() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(
        conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER)")
            .unwrap(),
    );
    assert_rows_affected(
        conn.execute("INSERT INTO t (id, val) VALUES (1, NULL)")
            .unwrap(),
        1,
    );

    let qr = query(&conn, "SELECT id FROM t WHERE val IN (1, 2, 3)");
    assert_eq!(qr.rows.len(), 0);

    let qr = query(&conn, "SELECT id FROM t WHERE val NOT IN (1, 2, 3)");
    assert_eq!(qr.rows.len(), 0);
}

#[test]
fn exists_with_data() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    let qr = query(&conn, "SELECT id FROM t1 WHERE EXISTS (SELECT 1 FROM t2)");
    assert_eq!(qr.rows.len(), 5);
}

#[test]
fn not_exists_with_data() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    let qr = query(
        &conn,
        "SELECT id FROM t1 WHERE NOT EXISTS (SELECT 1 FROM t2)",
    );
    assert_eq!(qr.rows.len(), 0);
}

#[test]
fn exists_short_circuit_multi_rows() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    let qr = query(
        &conn,
        "SELECT id FROM t1 WHERE EXISTS (SELECT val FROM t2 WHERE val > 100)",
    );
    assert_eq!(qr.rows.len(), 5);
}

#[test]
fn scalar_subquery_with_aggregate() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    let qr = query(
        &conn,
        "SELECT id FROM t1 WHERE val < (SELECT AVG(val) FROM t1)",
    );
    assert_eq!(get_ids(&qr), vec![1, 2]);
}

#[test]
fn scalar_subquery_null_comparison() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(
        conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER)")
            .unwrap(),
    );
    assert_ok(
        conn.execute("CREATE TABLE s (id INTEGER NOT NULL PRIMARY KEY)")
            .unwrap(),
    );
    assert_rows_affected(
        conn.execute("INSERT INTO t (id, val) VALUES (1, 10)")
            .unwrap(),
        1,
    );

    let qr = query(&conn, "SELECT id FROM t WHERE val = (SELECT id FROM s)");
    assert_eq!(qr.rows.len(), 0);
}

#[test]
fn scalar_subquery_in_arithmetic() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    let qr = query(
        &conn,
        "SELECT id FROM t1 WHERE val > (SELECT MIN(val) FROM t1) + 5",
    );
    assert_eq!(get_ids(&qr), vec![2, 3, 4, 5]);
}

#[test]
fn in_subquery_with_distinct() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    let qr = query(
        &conn,
        "SELECT DISTINCT val FROM t1 WHERE id IN (SELECT id FROM t2)",
    );
    assert_eq!(qr.rows.len(), 2);
}

#[test]
fn in_subquery_with_order_by_desc() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    let qr = query(
        &conn,
        "SELECT id FROM t1 WHERE id IN (SELECT id FROM t2) ORDER BY id DESC",
    );
    assert_eq!(qr.rows.len(), 2);
    assert_eq!(qr.rows[0][0], Value::Integer(4));
    assert_eq!(qr.rows[1][0], Value::Integer(2));
}

#[test]
fn in_subquery_with_offset() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    let qr = query(
        &conn,
        "SELECT id FROM t1 WHERE id NOT IN (SELECT id FROM t2) ORDER BY id LIMIT 2 OFFSET 1",
    );
    assert_eq!(qr.rows.len(), 2);
    assert_eq!(qr.rows[0][0], Value::Integer(3));
    assert_eq!(qr.rows[1][0], Value::Integer(5));
}

#[test]
fn in_subquery_combined_with_and() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    let qr = query(
        &conn,
        "SELECT id FROM t1 WHERE id IN (SELECT id FROM t2) AND val > 25",
    );
    assert_eq!(get_ids(&qr), vec![4]);
}

#[test]
fn in_subquery_combined_with_or() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    let qr = query(
        &conn,
        "SELECT id FROM t1 WHERE id IN (SELECT id FROM t2) OR val = 10",
    );
    assert_eq!(get_ids(&qr), vec![1, 2, 4]);
}

#[test]
fn delete_with_not_in_subquery() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    assert_rows_affected(
        conn.execute("DELETE FROM t1 WHERE id NOT IN (SELECT id FROM t2)")
            .unwrap(),
        3,
    );

    let qr = query(&conn, "SELECT id FROM t1 ORDER BY id");
    assert_eq!(get_ids(&qr), vec![2, 4]);
}

#[test]
fn delete_with_exists() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    assert_rows_affected(
        conn.execute("DELETE FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.id = 999)")
            .unwrap(),
        0,
    );

    let qr = query(&conn, "SELECT COUNT(*) FROM t1");
    assert_eq!(qr.rows[0][0], Value::Integer(5));
}

#[test]
fn update_set_with_scalar_subquery() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    assert_rows_affected(
        conn.execute("UPDATE t1 SET val = (SELECT MIN(val) FROM t2)")
            .unwrap(),
        5,
    );

    let qr = query(&conn, "SELECT DISTINCT val FROM t1");
    assert_eq!(qr.rows.len(), 1);
    assert_eq!(qr.rows[0][0], Value::Integer(200));
}

#[test]
fn update_where_exists() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    assert_rows_affected(
        conn.execute("UPDATE t1 SET val = 0 WHERE EXISTS (SELECT 1 FROM t2)")
            .unwrap(),
        5,
    );

    let qr = query(&conn, "SELECT DISTINCT val FROM t1");
    assert_eq!(qr.rows.len(), 1);
    assert_eq!(qr.rows[0][0], Value::Integer(0));
}

#[test]
fn update_where_not_exists() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    assert_rows_affected(
        conn.execute("UPDATE t1 SET val = 0 WHERE NOT EXISTS (SELECT 1 FROM t2)")
            .unwrap(),
        0,
    );
}

#[test]
fn in_list_single_value() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    let qr = query(&conn, "SELECT id FROM t1 WHERE id IN (3)");
    assert_eq!(get_ids(&qr), vec![3]);
}

#[test]
fn in_list_with_expressions() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    let qr = query(&conn, "SELECT id FROM t1 WHERE id IN (1 + 1, 2 + 2)");
    assert_eq!(get_ids(&qr), vec![2, 4]);
}

#[test]
fn not_in_list_with_null_only() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    let qr = query(&conn, "SELECT id FROM t1 WHERE id NOT IN (NULL)");
    assert_eq!(qr.rows.len(), 0);
}

#[test]
fn in_subquery_sees_uncommitted_data() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    conn.execute("BEGIN").unwrap();
    assert_rows_affected(
        conn.execute("INSERT INTO t2 (id, val) VALUES (1, 100)")
            .unwrap(),
        1,
    );

    let qr = query(&conn, "SELECT id FROM t1 WHERE id IN (SELECT id FROM t2)");
    assert_eq!(get_ids(&qr), vec![1, 2, 4]);

    conn.execute("ROLLBACK").unwrap();

    let qr = query(&conn, "SELECT id FROM t1 WHERE id IN (SELECT id FROM t2)");
    assert_eq!(get_ids(&qr), vec![2, 4]);
}

#[test]
fn delete_in_subquery_within_transaction() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    conn.execute("BEGIN").unwrap();
    assert_rows_affected(
        conn.execute("DELETE FROM t1 WHERE id IN (SELECT id FROM t2)")
            .unwrap(),
        2,
    );

    let qr = query(&conn, "SELECT id FROM t1 ORDER BY id");
    assert_eq!(get_ids(&qr), vec![1, 3, 5]);

    conn.execute("ROLLBACK").unwrap();

    let qr = query(&conn, "SELECT id FROM t1 ORDER BY id");
    assert_eq!(get_ids(&qr), vec![1, 2, 3, 4, 5]);
}

#[test]
fn update_in_subquery_within_transaction() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    conn.execute("BEGIN").unwrap();
    assert_rows_affected(
        conn.execute("UPDATE t1 SET val = 999 WHERE id IN (SELECT id FROM t2)")
            .unwrap(),
        2,
    );

    let qr = query(&conn, "SELECT val FROM t1 WHERE id = 2");
    assert_eq!(qr.rows[0][0], Value::Integer(999));

    conn.execute("COMMIT").unwrap();

    let qr = query(&conn, "SELECT val FROM t1 WHERE id = 2");
    assert_eq!(qr.rows[0][0], Value::Integer(999));
}

#[test]
fn in_subquery_scale() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(
        conn.execute("CREATE TABLE big (id INTEGER NOT NULL PRIMARY KEY, val INTEGER NOT NULL)")
            .unwrap(),
    );
    assert_ok(
        conn.execute("CREATE TABLE filter (id INTEGER NOT NULL PRIMARY KEY)")
            .unwrap(),
    );

    conn.execute("BEGIN").unwrap();
    for i in 0..200 {
        conn.execute(&format!(
            "INSERT INTO big (id, val) VALUES ({i}, {val})",
            val = i * 10
        ))
        .unwrap();
    }
    for i in (0..200).step_by(3) {
        conn.execute(&format!("INSERT INTO filter (id) VALUES ({i})"))
            .unwrap();
    }
    conn.execute("COMMIT").unwrap();

    let qr = query(
        &conn,
        "SELECT COUNT(*) FROM big WHERE id IN (SELECT id FROM filter)",
    );
    assert_eq!(qr.rows[0][0], Value::Integer(67));
}

#[test]
fn in_and_exists_combined() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    let qr = query(&conn,
        "SELECT id FROM t1 WHERE id IN (SELECT id FROM t2) AND EXISTS (SELECT 1 FROM t2 WHERE val > 300)"
    );
    assert_eq!(get_ids(&qr), vec![2, 4]);
}

#[test]
fn scalar_subquery_and_in_list() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    let qr = query(
        &conn,
        "SELECT id FROM t1 WHERE val > (SELECT MIN(val) FROM t1) * 2 OR id IN (1, 5)",
    );
    assert_eq!(get_ids(&qr), vec![1, 3, 4, 5]);
}

#[test]
fn not_in_and_not_exists() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    let qr = query(&conn,
        "SELECT id FROM t1 WHERE id NOT IN (SELECT id FROM t2) AND NOT EXISTS (SELECT 1 FROM t2 WHERE val > 9999)"
    );
    assert_eq!(get_ids(&qr), vec![1, 3, 5]);
}

#[test]
fn in_subquery_same_table() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    let qr = query(
        &conn,
        "SELECT id FROM t1 WHERE val IN (SELECT val FROM t1 WHERE id <= 2)",
    );
    assert_eq!(get_ids(&qr), vec![1, 2]);
}

#[test]
fn scalar_subquery_same_table() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();
    setup_tables(&conn);

    let qr = query(
        &conn,
        "SELECT id FROM t1 WHERE val = (SELECT MAX(val) FROM t1)",
    );
    assert_eq!(get_ids(&qr), vec![5]);
}

#[test]
fn nested_in_subquery() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(
        conn.execute("CREATE TABLE a (id INTEGER NOT NULL PRIMARY KEY)")
            .unwrap(),
    );
    assert_ok(
        conn.execute("CREATE TABLE b (id INTEGER NOT NULL PRIMARY KEY)")
            .unwrap(),
    );
    assert_ok(
        conn.execute("CREATE TABLE c (id INTEGER NOT NULL PRIMARY KEY)")
            .unwrap(),
    );
    assert_rows_affected(
        conn.execute("INSERT INTO a (id) VALUES (1), (2), (3), (4), (5)")
            .unwrap(),
        5,
    );
    assert_rows_affected(
        conn.execute("INSERT INTO b (id) VALUES (1), (2), (3)")
            .unwrap(),
        3,
    );
    assert_rows_affected(
        conn.execute("INSERT INTO c (id) VALUES (1), (2)").unwrap(),
        2,
    );

    let qr = query(
        &conn,
        "SELECT id FROM a WHERE id IN (SELECT id FROM b WHERE id IN (SELECT id FROM c))",
    );
    assert_eq!(get_ids(&qr), vec![1, 2]);
}

#[test]
fn in_with_boolean_column() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(
        conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, active BOOLEAN NOT NULL)")
            .unwrap(),
    );
    assert_ok(
        conn.execute("CREATE TABLE flags (id INTEGER NOT NULL PRIMARY KEY, flag BOOLEAN NOT NULL)")
            .unwrap(),
    );
    assert_rows_affected(
        conn.execute("INSERT INTO t (id, active) VALUES (1, TRUE), (2, FALSE), (3, TRUE)")
            .unwrap(),
        3,
    );
    assert_rows_affected(
        conn.execute("INSERT INTO flags (id, flag) VALUES (1, TRUE)")
            .unwrap(),
        1,
    );

    let qr = query(
        &conn,
        "SELECT id FROM t WHERE active IN (SELECT flag FROM flags)",
    );
    assert_eq!(get_ids(&qr), vec![1, 3]);
}

#[test]
fn having_with_scalar_subquery() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(conn.execute(
        "CREATE TABLE sales (id INTEGER NOT NULL PRIMARY KEY, region TEXT NOT NULL, amount INTEGER NOT NULL)"
    ).unwrap());
    assert_ok(
        conn.execute(
            "CREATE TABLE thresholds (id INTEGER NOT NULL PRIMARY KEY, min_total INTEGER NOT NULL)",
        )
        .unwrap(),
    );
    assert_rows_affected(conn.execute(
        "INSERT INTO sales (id, region, amount) VALUES (1, 'east', 100), (2, 'east', 200), (3, 'west', 50), (4, 'west', 25)"
    ).unwrap(), 4);
    assert_rows_affected(
        conn.execute("INSERT INTO thresholds (id, min_total) VALUES (1, 100)")
            .unwrap(),
        1,
    );

    let qr = query(&conn,
        "SELECT region, SUM(amount) FROM sales GROUP BY region HAVING SUM(amount) > (SELECT min_total FROM thresholds WHERE id = 1)"
    );
    assert_eq!(qr.rows.len(), 1);
    assert_eq!(qr.rows[0][0], Value::Text("east".into()));
    assert_eq!(qr.rows[0][1], Value::Integer(300));
}

#[test]
fn in_subquery_with_real_values() {
    let dir = tempfile::tempdir().unwrap();
    let db = create_db(dir.path());
    let conn = Connection::open(&db).unwrap();

    assert_ok(
        conn.execute("CREATE TABLE prices (id INTEGER NOT NULL PRIMARY KEY, price REAL NOT NULL)")
            .unwrap(),
    );
    assert_ok(
        conn.execute(
            "CREATE TABLE discounts (id INTEGER NOT NULL PRIMARY KEY, price REAL NOT NULL)",
        )
        .unwrap(),
    );
    assert_rows_affected(
        conn.execute("INSERT INTO prices (id, price) VALUES (1, 9.99), (2, 19.99), (3, 29.99)")
            .unwrap(),
        3,
    );
    assert_rows_affected(
        conn.execute("INSERT INTO discounts (id, price) VALUES (1, 9.99), (2, 29.99)")
            .unwrap(),
        2,
    );

    let qr = query(
        &conn,
        "SELECT id FROM prices WHERE price IN (SELECT price FROM discounts)",
    );
    assert_eq!(get_ids(&qr), vec![1, 3]);
}
