#include "testlib.h" RENDER(Request& context) { u64 passed = 0; u64 failed = 0; u64 skipped = 0; auto check = [&](String name, bool ok, String detail) { site_tests_case(name, ok ? "pass" : "fail", detail); if(ok) passed++; else failed++; }; site_tests_page_start("SQLite", "SQLite connector coverage for prepared params, result typing, metadata, and cleanup."); String db_path = "/tmp/uce-site-tests-sqlite.sqlite"; file_unlink(db_path); file_unlink(db_path + "-wal"); file_unlink(db_path + "-shm"); SQLite* db = sqlite_connect(db_path); check("sqlite_connect()", db && db->connection != 0 && sqlite_error(db) == "connected", sqlite_error(db)); sqlite_query(db, "create table users(id integer primary key autoincrement, email text not null unique, visits integer not null, rating real not null)"); check("sqlite create table", sqlite_error(db) == "ok", sqlite_error(db)); sqlite_query(db, "create table dropped(id integer); insert into dropped values(1);"); String multi_statement_error = sqlite_error(db); StringMap dropped_params; dropped_params["name"] = "dropped"; DValue dropped_tables = sqlite_query(db, "select name from sqlite_master where type = 'table' and name = :name", dropped_params); check("sqlite_query() rejects multi-statement SQL", multi_statement_error.find("exactly one SQL statement") != String::npos && dropped_tables["0"]["name"].to_string() == "", multi_statement_error + " tables=" + json_encode(dropped_tables)); StringMap ada; ada["email"] = "ada@example.test"; ada["visits"] = "7"; ada["rating"] = "4.5"; sqlite_query(db, "insert into users(email, visits, rating) values(:email, :visits, :rating)", ada); u64 ada_id = sqlite_insert_id(db); check("sqlite named insert", ada_id == 1 && sqlite_affected_rows(db) == 1, "id=" + std::to_string(ada_id) + " affected=" + std::to_string((u64)sqlite_affected_rows(db)) + " error=" + sqlite_error(db)); StringMap bob; bob["email"] = "bob@example.test"; bob["visits"] = "3"; bob["rating"] = "2.25"; sqlite_query(db, "insert into users(email, visits, rating) values(:email, :visits, :rating)", bob); check("sqlite second insert", sqlite_insert_id(db) == 2 && sqlite_affected_rows(db) == 1, "id=" + std::to_string(sqlite_insert_id(db))); StringMap query_params; query_params["min_visits"] = "4"; DValue rows = sqlite_query(db, "select id, email, visits, rating from users where visits >= :min_visits order by id", query_params); check("sqlite_query() rows", rows.is_array() && rows["0"]["email"].to_string() == "ada@example.test" && rows["0"]["visits"].to_s64() == 7 && rows["0"]["rating"].to_f64() > 4.49, json_encode(rows)); sqlite_query(db, "select id from users where id = ?", query_params); String positional_error = sqlite_error(db); check("sqlite_query() rejects positional placeholders", positional_error.find("positional ? placeholders are not supported") != String::npos, positional_error); sqlite_query(db, "select id from users where id = @id", query_params); String non_colon_error = sqlite_error(db); check("sqlite_query() rejects non-colon placeholders", non_colon_error.find("only supports :name placeholders") != String::npos, non_colon_error); sqlite_query(db, "update users set visits = visits + 1 where email = :email", ada); check("sqlite_affected_rows()", sqlite_affected_rows(db) == 1, std::to_string((u64)sqlite_affected_rows(db))); sqlite_query(db, "select nope from missing_table"); check("sqlite_error()", sqlite_error(db).find("sqlite prepare failed") != String::npos && sqlite_error(db).find("no such table") != String::npos, sqlite_error(db)); sqlite_disconnect(db); String cleanup_db_path = db_path + ".cleanup"; file_unlink(cleanup_db_path); file_unlink(cleanup_db_path + "-wal"); file_unlink(cleanup_db_path + "-shm"); SQLite* cleanup_db = sqlite_connect(cleanup_db_path); sqlite_query(cleanup_db, "create table cleanup_probe(id integer)"); cleanup_sqlite_connections(); check("sqlite request cleanup releases tracked wrappers", context.resources.sqlite_connections.size() == 0, "tracked=" + std::to_string((u64)context.resources.sqlite_connections.size())); sqlite_disconnect(cleanup_db); file_unlink(cleanup_db_path); file_unlink(cleanup_db_path + "-wal"); file_unlink(cleanup_db_path + "-shm"); file_unlink(db_path); file_unlink(db_path + "-wal"); file_unlink(db_path + "-shm"); site_tests_summary(passed, failed, skipped, "SQLite tests write only under /tmp/uce-site-tests-sqlite.sqlite and clean up WAL/SHM sidecars."); site_tests_page_end(); }