← All tasks
cppcodex/cpp-t1 #6Not a task: already works

SQLite Database Manager (cpp, written by Codex)

envgap__codex__cpp-t1-6

Written by a coding agent; not on GitHubWritten 2026-03-02

01 / FAILURE SIGNATURE

As the study recorded it

No identifying execution failure has been captured.
Not a benchmark task.
  • The project already builds and runs before the fix, so there is nothing to repair.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
CMakeLists.txt
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

codex/cpp-t1 #6 · read the task the agent was given
Codex wrote this cpp project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: SQLite Database Manager

Write a program that provides a command-line interface for managing SQLite databases, supporting table creation, CRUD operations, schema inspection, data import/export, and query execution.

FUNCTIONAL REQUIREMENTS:
- Accept a database file path as a command-line argument (create it if it does not exist)
- Support creating tables with typed columns (TEXT, INTEGER, REAL, BLOB) and constraints (PRIMARY KEY, NOT NULL, UNIQUE, DEFAULT)
- Support INSERT, SELECT, UPDATE, and DELETE operations via command-line subcommands
- Support executing arbitrary SQL queries via a --query flag
- Implement schema inspection: list all tables, show column details for a given table, show row counts
- Import data from a CSV file into a specified table via --import flag, auto-detecting column types
- Export a table or query result to CSV via --export flag
- Support basic transactions: --begin, --commit, --rollback flags for grouping operations
- Print query results as formatted tables to console with column headers and aligned values
- If no database path is given, create a sample database with at least three related tables (e.g., users, orders, products), populate with sample data, and run demonstration queries showing joins, aggregations, and filtering
- Handle database errors gracefully with clear messages distinguishing connection, schema, constraint, and query errors

Create a complete C++ project for a clean Ubuntu 22.04 machine with only G++ 12+ and CMake 3.22+ installed. Include:
- Source code
- CMakeLists.txt with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output

04 / LABELS

Labels from the report text only; not yet run

No supported category has been assigned.

Label rules and the text that matched
[]

05 / FILES

The project as the agent wrote it

3 files, exactly as written, before any repair.

CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(sqlite_database_manager_cpp VERSION 1.0.0 LANGUAGES C CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

include(FetchContent)

# Pinned SQLite amalgamation release (3.46.0)
FetchContent_Declare(
  sqlite_amalgamation
  URL https://www.sqlite.org/2024/sqlite-amalgamation-3460000.zip
)
FetchContent_MakeAvailable(sqlite_amalgamation)

add_library(sqlite3 STATIC
  ${sqlite_amalgamation_SOURCE_DIR}/sqlite3.c
)
target_include_directories(sqlite3 PUBLIC ${sqlite_amalgamation_SOURCE_DIR})

add_executable(sqlite_manager src/main.cpp)
target_link_libraries(sqlite_manager PRIVATE sqlite3)
README.md
# SQLite Database Manager (C++)

SQLite CLI manager implemented with the SQLite C API.

## Requirements

- Ubuntu 22.04
- G++ 12+
- CMake 3.22+
- Network access during CMake configure (for SQLite source fetch)

## Dependencies

- Direct:
  - SQLite amalgamation `3.46.0` from `https://www.sqlite.org/2024/sqlite-amalgamation-3460000.zip`
- Transitive:
  - none

The dependency is pinned by exact URL in `CMakeLists.txt`.

## Build

```bash
cmake -S . -B build
cmake --build build
```

## Run

No DB path: creates sample DB and runs demo queries.

```bash
./build/sqlite_manager
```

Use a specific DB:

```bash
./build/sqlite_manager ./app.db --list-tables
```

## Commands

Create table:

```bash
./build/sqlite_manager ./app.db --create-table users --columns "id:INTEGER PRIMARY KEY,name:TEXT NOT NULL,email:TEXT UNIQUE"
```

Insert:

```bash
./build/sqlite_manager ./app.db --insert users --values "id=1,name=Alice,email=alice@example.com"
```

Select:

```bash
./build/sqlite_manager ./app.db --select users --columns "id,name,email" --where "id >= 1" --limit 10
```

Update:

```bash
./build/sqlite_manager ./app.db --update users --set "name=Alice Updated" --where "id = 1"
```

Delete:

```bash
./build/sqlite_manager ./app.db --delete users --where "id = 1"
```

Run arbitrary SQL:

```bash
./build/sqlite_manager ./app.db --query "SELECT name FROM sqlite_master WHERE type='table'"
```

Schema inspection:

```bash
./build/sqlite_manager ./app.db --list-tables
./build/sqlite_manager ./app.db --describe users
./build/sqlite_manager ./app.db --row-count users
```

Import CSV:

```bash
./build/sqlite_manager ./app.db --import ./users.csv --table imported_users
```

Export CSV:

```bash
./build/sqlite_manager ./app.db --export ./users_out.csv --table users
./build/sqlite_manager ./app.db --export ./query_out.csv --query "SELECT * FROM users WHERE id > 5"
```

Transactions:

```bash
./build/sqlite_manager ./app.db --begin --insert users --values "id=3,name=Carol,email=carol@example.com" --commit
./build/sqlite_manager ./app.db --begin --delete users --where "id=3" --rollback
```

## Expected Output

- Aligned query result tables with headers
- Categorized DB errors (`Connection Error`, `Schema Error`, `Constraint Error`, `Query Error`)
- Automatic DB creation if missing
src/main.cpp
#include <algorithm>
#include <cctype>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <regex>
#include <set>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>

#include <sqlite3.h>

namespace {

struct ParsedArgs {
    std::map<std::string, std::string> options;
    std::set<std::string> flags;
    std::vector<std::string> positional;
};

struct QueryResult {
    std::vector<std::string> columns;
    std::vector<std::vector<std::string>> rows;
};

enum class ValueType { Null, Integer, Real, Text };

struct SqlValue {
    ValueType type = ValueType::Text;
    long long integer = 0;
    double real = 0.0;
    std::string text;
};

ParsedArgs parseArgs(int argc, char** argv) {
    ParsedArgs parsed;
    for (int i = 1; i < argc; i++) {
        std::string token = argv[i];
        if (token.rfind("--", 0) == 0) {
            std::string key = token.substr(2);
            if (i + 1 < argc && std::string(argv[i + 1]).rfind("--", 0) != 0) {
                parsed.options[key] = argv[i + 1];
                i++;
            } else {
                parsed.flags.insert(key);
            }
        } else {
            parsed.positional.push_back(token);
        }
    }
    return parsed;
}

std::string escapeIdent(const std::string& name) {
    std::string out = "\"";
    for (char c : name) {
        if (c == '"') out += "\"\"";
        else out += c;
    }
    out += "\"";
    return out;
}

std::string classifyError(const std::string& message) {
    std::string lower = message;
    std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
    if (lower.find("no such table") != std::string::npos || lower.find("no such column") != std::string::npos ||
        lower.find("already exists") != std::string::npos) {
        return "Schema Error";
    }
    if (lower.find("constraint failed") != std::string::npos || lower.find("not null") != std::string::npos ||
        lower.find("unique") != std::string::npos) {
        return "Constraint Error";
    }
    if (lower.find("syntax error") != std::string::npos || lower.find("near") != std::string::npos) {
        return "Query Error";
    }
    return "Connection Error";
}

std::vector<std::string> parseCsvLine(const std::string& line) {
    std::vector<std::string> fields;
    std::string current;
    bool inQuotes = false;
    for (std::size_t i = 0; i < line.size(); i++) {
        char ch = line[i];
        if (ch == '"') {
            if (inQuotes && i + 1 < line.size() && line[i + 1] == '"') {
                current += '"';
                i++;
            } else {
                inQuotes = !inQuotes;
            }
        } else if (ch == ',' && !inQuotes) {
            fields.push_back(current);
            current.clear();
        } else {
            current += ch;
        }
    }
    fields.push_back(current);
    return fields;
}

std::string toCsvLine(const std::vector<std::string>& fields) {
    std::ostringstream out;
    for (std::size_t i = 0; i < fields.size(); i++) {
        if (i) out << ",";
        const std::string& value = fields[i];
        bool quote = value.find(',') != std::string::npos || value.find('"') != std::string::npos || value.find('\n') != std::string::npos;
        if (quote) {
            out << '"';
            for (char c : value) {
                if (c == '"') out << "\"\"";
                else out << c;
            }
            out << '"';
        } else {
            out << value;
        }
    }
    return out.str();
}

std::string trim(const std::string& input) {
    std::size_t start = 0;
    while (start < input.size() && std::isspace(static_cast<unsigned char>(input[start]))) start++;
    std::size_t end = input.size();
    while (end > start && std::isspace(static_cast<unsigned char>(input[end - 1]))) end--;
    return input.substr(start, end - start);
}

SqlValue parseValue(const std::string& raw) {
    std::string text = trim(raw);
    if (text.empty()) return SqlValue{ValueType::Text, 0, 0.0, ""};
    std::string lower = text;
    std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
    if (lower == "null") return SqlValue{ValueType::Null, 0, 0.0, ""};
    if (lower == "true") return SqlValue{ValueType::Integer, 1, 0.0, ""};
    if (lower == "false") return SqlValue{ValueType::Integer, 0, 0.0, ""};
    if (std::regex_match(text, std::regex(R"(^-?\d+$)"))) {
        return SqlValue{ValueType::Integer, std::stoll(text), 0.0, ""};
    }
    if (std::regex_match(text, std::regex(R"(^-?\d+\.\d+$)"))) {
        return SqlValue{ValueType::Real, 0, std::stod(text), ""};
    }
    if ((text.front() == '"' && text.back() == '"') || (text.front() == '\'' && text.back() == '\'')) {
        return SqlValue{ValueType::Text, 0, 0.0, text.substr(1, text.size() - 2)};
    }
    return SqlValue{ValueType::Text, 0, 0.0, text};
}

void checkRc(int rc, sqlite3* db, const std::string& context) {
    if (rc != SQLITE_OK && rc != SQLITE_DONE && rc != SQLITE_ROW) {
        throw std::runtime_error(context + ": " + sqlite3_errmsg(db));
    }
}

void executeNonQuery(sqlite3* db, const std::string& sql) {
    char* err = nullptr;
    int rc = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &err);
    if (rc != SQLITE_OK) {
        std::string msg = err ? err : "Unknown SQLite error";
        sqlite3_free(err);
        throw std::runtime_error(msg);
    }
}

QueryResult executeQuery(sqlite3* db, const std::string& sql) {
    QueryResult result;
    sqlite3_stmt* stmt = nullptr;
    int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr);
    checkRc(rc, db, "prepare");

    int cols = sqlite3_column_count(stmt);
    for (int i = 0; i < cols; i++) {
        const char* col = sqlite3_column_name(stmt, i);
        result.columns.push_back(col ? col : "");
    }

    while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
        std::vector<std::string> row;
        for (int i = 0; i < cols; i++) {
            if (sqlite3_column_type(stmt, i) == SQLITE_NULL) {
                row.push_back("NULL");
            } else {
                const unsigned char* txt = sqlite3_column_text(stmt, i);
                row.push_back(txt ? reinterpret_cast<const char*>(txt) : "");
            }
        }
        result.rows.push_back(row);
    }
    if (rc != SQLITE_DONE) {
        std::string msg = sqlite3_errmsg(db);
        sqlite3_finalize(stmt);
        throw std::runtime_error(msg);
    }
    sqlite3_finalize(stmt);
    return result;
}

void printTable(const QueryResult& result) {
    if (result.columns.empty()) {
        std::cout << "(no columns)\n";
        return;
    }
    std::vector<std::size_t> widths;
    widths.reserve(result.columns.size());
    for (const auto& c : result.columns) widths.push_back(c.size());
    for (const auto& row : result.rows) {
        for (std::size_t i = 0; i < row.size(); i++) {
            widths[i] = std::max(widths[i], row[i].size());
        }
    }

    std::ostringstream sep;
    sep << "+-";
    for (std::size_t i = 0; i < widths.size(); i++) {
        sep << std::string(widths[i], '-');
        sep << (i + 1 < widths.size() ? "-+-" : "-+");
    }

    auto printRow = [&](const std::vector<std::string>& values) {
        std::cout << "| ";
        for (std::size_t i = 0; i < widths.size(); i++) {
            std::string cell = i < values.size() ? values[i] : "";
            std::cout << cell << std::string(widths[i] > cell.size() ? widths[i] - cell.size() : 0, ' ');
            if (i + 1 < widths.size()) std::cout << " | ";
        }
        std::cout << " |\n";
    };

    std::cout << sep.str() << "\n";
    printRow(result.columns);
    std::cout << sep.str() << "\n";
    for (const auto& row : result.rows) printRow(row);
    std::cout << sep.str() << "\n";
    std::cout << result.rows.size() << " row(s)\n";
}

bool tableExists(sqlite3* db, const std::string& tableName) {
    sqlite3_stmt* stmt = nullptr;
    int rc = sqlite3_prepare_v2(db, "SELECT name FROM sqlite_master WHERE type='table' AND name = ?;", -1, &stmt, nullptr);
    checkRc(rc, db, "prepare tableExists");
    sqlite3_bind_text(stmt, 1, tableName.c_str(), -1, SQLITE_TRANSIENT);
    rc = sqlite3_step(stmt);
    bool exists = (rc == SQLITE_ROW);
    sqlite3_finalize(stmt);
    return exists;
}

std::vector<std::pair<std::string, SqlValue>> parseKeyValueSpec(const std::string& spec) {
    std::vector<std::pair<std::string, SqlValue>> items;
    for (const auto& part : parseCsvLine(spec)) {
        std::size_t eq = part.find('=');
        if (eq == std::string::npos || eq == 0) throw std::runtime_error("Invalid key=value pair: " + part);
        std::string key = trim(part.substr(0, eq));
        std::string value = trim(part.substr(eq + 1));
        items.push_back({key, parseValue(value)});
    }
    return items;
}

std::vector<std::pair<std::string, std::string>> parseColumnSpec(const std::string& spec) {
    std::vector<std::pair<std::string, std::string>> cols;
    for (const auto& part : parseCsvLine(spec)) {
        std::string t = trim(part);
        std::size_t colon = t.find(':');
        if (colon == std::string::npos || colon == 0 || colon + 1 >= t.size()) {
            throw std::runtime_error("Invalid column definition: " + t);
        }
        cols.push_back({trim(t.substr(0, colon)), trim(t.substr(colon + 1))});
    }
    return cols;
}

void bindValue(sqlite3_stmt* stmt, int index, const SqlValue& v) {
    switch (v.type) {
        case ValueType::Null:
            sqlite3_bind_null(stmt, index);
            break;
        case ValueType::Integer:
            sqlite3_bind_int64(stmt, index, v.integer);
            break;
        case ValueType::Real:
            sqlite3_bind_double(stmt, index, v.real);
            break;
        case ValueType::Text:
            sqlite3_bind_text(stmt, index, v.text.c_str(), -1, SQLITE_TRANSIENT);
            break;
    }
}

std::string detectType(const std::vector<std::string>& values) {
    std::vector<std::string> nonEmpty;
    for (const auto& v : values) {
        std::string t = trim(v);
        if (!t.empty()) nonEmpty.push_back(t);
    }
    if (nonEmpty.empty()) return "TEXT";
    auto allInt = std::all_of(nonEmpty.begin(), nonEmpty.end(), [](const std::string& v) {
        return std::regex_match(v, std::regex(R"(^-?\d+$)"));
    });
    if (allInt) return "INTEGER";
    auto allReal = std::all_of(nonEmpty.begin(), nonEmpty.end(), [](const std::string& v) {
        return std::regex_match(v, std::regex(R"(^-?\d+(\.\d+)?$)"));
    });
    if (allReal) return "REAL";
    auto allBlob = std::all_of(nonEmpty.begin(), nonEmpty.end(), [](const std::string& v) {
        return std::regex_match(v, std::regex(R"(^0x[0-9a-fA-F]+$)"));
    });
    if (allBlob) return "BLOB";
    return "TEXT";
}

bool doCreateTable(sqlite3* db, const ParsedArgs& args) {
    auto it = args.options.find("create-table");
    if (it == args.options.end()) return false;
    auto colIt = args.options.find("columns");
    if (colIt == args.options.end()) throw std::runtime_error("--create-table requires --columns");

    auto cols = parseColumnSpec(colIt->second);
    std::ostringstream sql;
    sql << "CREATE TABLE IF NOT EXISTS " << escapeIdent(it->second) << " (";
    for (std::size_t i = 0; i < cols.size(); i++) {
        if (i) sql << ", ";
        sql << escapeIdent(cols[i].first) << " " << cols[i].second;
    }
    sql << ");";
    executeNonQuery(db, sql.str());
    std::cout << "Created/verified table: " << it->second << "\n";
    return true;
}

bool doInsert(sqlite3* db, const ParsedArgs& args) {
    auto it = args.options.find("insert");
    if (it == args.options.end()) return false;
    auto valIt = args.options.find("values");
    if (valIt == args.options.end()) throw std::runtime_error("--insert requires --values");
    auto entries = parseKeyValueSpec(valIt->second);

    std::ostringstream sql;
    sql << "INSERT INTO " << escapeIdent(it->second) << " (";
    for (std::size_t i = 0; i < entries.size(); i++) {
        if (i) sql << ", ";
        sql << escapeIdent(entries[i].first);
    }
    sql << ") VALUES (";
    for (std::size_t i = 0; i < entries.size(); i++) {
        if (i) sql << ", ";
        sql << "?";
    }
    sql << ");";

    sqlite3_stmt* stmt = nullptr;
    int rc = sqlite3_prepare_v2(db, sql.str().c_str(), -1, &stmt, nullptr);
    checkRc(rc, db, "prepare insert");
    for (std::size_t i = 0; i < entries.size(); i++) {
        bindValue(stmt, static_cast<int>(i + 1), entries[i].second);
    }
    rc = sqlite3_step(stmt);
    if (rc != SQLITE_DONE) {
        std::string msg = sqlite3_errmsg(db);
        sqlite3_finalize(stmt);
        throw std::runtime_error(msg);
    }
    sqlite3_finalize(stmt);
    std::cout << "Inserted 1 row into " << it->second << "\n";
    return true;
}

bool doSelect(sqlite3* db, const ParsedArgs& args) {
    auto it = args.options.find("select");
    if (it == args.options.end()) return false;
    std::string columns = "*";
    if (auto c = args.options.find("columns"); c != args.options.end()) columns = c->second;
    std::ostringstream sql;
    sql << "SELECT " << columns << " FROM " << escapeIdent(it->second);
    if (auto w = args.options.find("where"); w != args.options.end()) sql << " WHERE " << w->second;
    if (auto l = args.options.find("limit"); l != args.options.end()) sql << " LIMIT " << std::stoi(l->second);
    sql << ";";
    printTable(executeQuery(db, sql.str()));
    return true;
}

bool doUpdate(sqlite3* db, const ParsedArgs& args) {
    auto it = args.options.find("update");
    if (it == args.options.end()) return false;
    auto setIt = args.options.find("set");
    if (setIt == args.options.end()) throw std::runtime_error("--update requires --set");
    auto entries = parseKeyValueSpec(setIt->second);

    std::ostringstream sql;
    sql << "UPDATE " << escapeIdent(it->second) << " SET ";
    for (std::size_t i = 0; i < entries.size(); i++) {
        if (i) sql << ", ";
        sql << escapeIdent(entries[i].first) << " = ?";
    }
    if (auto w = args.options.find("where"); w != args.options.end()) sql << " WHERE " << w->second;
    sql << ";";

    sqlite3_stmt* stmt = nullptr;
    int rc = sqlite3_prepare_v2(db, sql.str().c_str(), -1, &stmt, nullptr);
    checkRc(rc, db, "prepare update");
    for (std::size_t i = 0; i < entries.size(); i++) bindValue(stmt, static_cast<int>(i + 1), entries[i].second);
    rc = sqlite3_step(stmt);
    if (rc != SQLITE_DONE) {
        std::string msg = sqlite3_errmsg(db);
        sqlite3_finalize(stmt);
        throw std::runtime_error(msg);
    }
    sqlite3_finalize(stmt);
    long long changed = sqlite3_changes64(db);
    std::cout << "Updated " << changed << " row(s) in " << it->second << "\n";
    return true;
}

bool doDelete(sqlite3* db, const ParsedArgs& args) {
    auto it = args.options.find("delete");
    if (it == args.options.end()) return false;
    std::ostringstream sql;
    sql << "DELETE FROM " << escapeIdent(it->second);
    if (auto w = args.options.find("where"); w != args.options.end()) sql << " WHERE " << w->second;
    sql << ";";
    executeNonQuery(db, sql.str());
    long long changed = sqlite3_changes64(db);
    std::cout << "Deleted " << changed << " row(s) from " << it->second << "\n";
    return true;
}

bool doImport(sqlite3* db, const ParsedArgs& args) {
    auto it = args.options.find("import");
    if (it == args.options.end()) return false;
    auto tableIt = args.options.find("table");
    if (tableIt == args.options.end()) throw std::runtime_error("--import requires --table");
    std::filesystem::path csvPath = std::filesystem::absolute(it->second);
    if (!std::filesystem::exists(csvPath)) throw std::runtime_error("CSV file not found: " + csvPath.string());

    std::ifstream in(csvPath);
    if (!in.is_open()) throw std::runtime_error("Failed to open CSV: " + csvPath.string());
    std::vector<std::vector<std::string>> rows;
    std::string line;
    while (std::getline(in, line)) {
        if (line.empty()) continue;
        rows.push_back(parseCsvLine(line));
    }
    if (rows.empty()) throw std::runtime_error("CSV is empty.");
    std::vector<std::string> headers = rows[0];
    std::vector<std::vector<std::string>> data(rows.begin() + 1, rows.end());
    const std::string& table = tableIt->second;

    if (!tableExists(db, table)) {
        std::ostringstream createSql;
        createSql << "CREATE TABLE " << escapeIdent(table) << " (";
        for (std::size_t i = 0; i < headers.size(); i++) {
            if (i) createSql << ", ";
            std::vector<std::string> colVals;
            for (const auto& row : data) colVals.push_back(i < row.size() ? row[i] : "");
            createSql << escapeIdent(headers[i]) << " " << detectType(colVals);
        }
        createSql << ");";
        executeNonQuery(db, createSql.str());
    }

    if (!data.empty()) {
        std::ostringstream insertSql;
        insertSql << "INSERT INTO " << escapeIdent(table) << " (";
        for (std::size_t i = 0; i < headers.size(); i++) {
            if (i) insertSql << ", ";
            insertSql << escapeIdent(headers[i]);
        }
        insertSql << ") VALUES (";
        for (std::size_t i = 0; i < headers.size(); i++) {
            if (i) insertSql << ", ";
            insertSql << "?";
        }
        insertSql << ");";

        sqlite3_stmt* stmt = nullptr;
        int rc = sqlite3_prepare_v2(db, insertSql.str().c_str(), -1, &stmt, nullptr);
        checkRc(rc, db, "prepare import");
        for (const auto& row : data) {
            for (std::size_t i = 0; i < headers.size(); i++) {
                std::string value = i < row.size() ? row[i] : "";
                bindValue(stmt, static_cast<int>(i + 1), parseValue(value));
            }
            rc = sqlite3_step(stmt);
            if (rc != SQLITE_DONE) {
                std::string msg = sqlite3_errmsg(db);
                sqlite3_finalize(stmt);
                throw std::runtime_error(msg);
            }
            sqlite3_reset(stmt);
            sqlite3_clear_bindings(stmt);
        }
        sqlite3_finalize(stmt);
    }
    std::cout << "Imported " << data.size() << " row(s) into " << table << "\n";
    return true;
}

bool doExport(sqlite3* db, const ParsedArgs& args) {
    auto it = args.options.find("export");
    if (it == args.options.end()) return false;
    std::string query;
    if (auto q = args.options.find("query"); q != args.options.end()) {
        query = q->second;
    } else if (auto t = args.options.find("table"); t != args.options.end()) {
        query = "SELECT * FROM " + escapeIdent(t->second) + ";";
    } else {
        throw std::runtime_error("--export requires either --table or --query");
    }

    QueryResult res = executeQuery(db, query);
    std::filesystem::path outPath = std::filesystem::absolute(it->second);
    std::ofstream out(outPath);
    if (!out.is_open()) throw std::runtime_error("Failed to open output CSV: " + outPath.string());
    out << toCsvLine(res.columns) << "\n";
    for (const auto& row : res.rows) out << toCsvLine(row) << "\n";
    out.close();
    std::cout << "Exported " << res.rows.size() << " row(s) to " << outPath.string() << "\n";
    return true;
}

void inspectSchema(sqlite3* db, const ParsedArgs& args) {
    if (args.flags.count("list-tables")) {
        QueryResult tables = executeQuery(db, "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name;");
        if (tables.rows.empty()) {
            std::cout << "No user tables found.\n";
        } else {
            QueryResult out;
            out.columns = {"table", "row_count"};
            for (const auto& row : tables.rows) {
                std::string table = row[0];
                QueryResult count = executeQuery(db, "SELECT COUNT(*) AS row_count FROM " + escapeIdent(table) + ";");
                out.rows.push_back({table, count.rows.empty() ? "0" : count.rows[0][0]});
            }
            printTable(out);
        }
    }
    if (auto d = args.options.find("describe"); d != args.options.end()) {
        printTable(executeQuery(db, "PRAGMA table_info(" + escapeIdent(d->second) + ");"));
    }
    if (auto r = args.options.find("row-count"); r != args.options.end()) {
        printTable(executeQuery(db, "SELECT COUNT(*) AS row_count FROM " + escapeIdent(r->second) + ";"));
    }
}

void buildSampleDb(sqlite3* db) {
    executeNonQuery(db, R"(
        PRAGMA foreign_keys = ON;
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            email TEXT UNIQUE NOT NULL
        );
        CREATE TABLE IF NOT EXISTS products (
            id INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            price REAL NOT NULL CHECK(price >= 0)
        );
        CREATE TABLE IF NOT EXISTS orders (
            id INTEGER PRIMARY KEY,
            user_id INTEGER NOT NULL REFERENCES users(id),
            product_id INTEGER NOT NULL REFERENCES products(id),
            quantity INTEGER NOT NULL DEFAULT 1,
            created_at TEXT NOT NULL
        );
        DELETE FROM orders;
        DELETE FROM users;
        DELETE FROM products;
    )");

    executeNonQuery(db, "INSERT INTO users(id, name, email) VALUES (1, 'Alice', 'alice@example.com'), (2, 'Bob', 'bob@example.com'), (3, 'Carol', 'carol@example.com');");
    executeNonQuery(db, "INSERT INTO products(id, name, price) VALUES (1, 'Keyboard', 79.99), (2, 'Monitor', 249.50), (3, 'Mouse', 29.95);");
    executeNonQuery(db, "INSERT INTO orders(id, user_id, product_id, quantity, created_at) VALUES "
                        "(1, 1, 1, 2, '2026-02-01T10:00:00Z'), "
                        "(2, 1, 2, 1, '2026-02-02T11:30:00Z'), "
                        "(3, 2, 3, 3, '2026-02-03T15:45:00Z'), "
                        "(4, 3, 2, 1, '2026-02-04T17:15:00Z');");
}

void runDemo(sqlite3* db) {
    std::cout << "No database path provided. Creating sample database and running demo queries.\n\n";
    buildSampleDb(db);

    std::cout << "Demo 1: Join users, orders, products\n";
    printTable(executeQuery(db, R"(
        SELECT o.id AS order_id, u.name AS user_name, p.name AS product_name, o.quantity, p.price,
               ROUND(o.quantity * p.price, 2) AS total
        FROM orders o
        JOIN users u ON u.id = o.user_id
        JOIN products p ON p.id = o.product_id
        ORDER BY o.id;
    )"));

    std::cout << "\nDemo 2: Aggregation by user\n";
    printTable(executeQuery(db, R"(
        SELECT u.name, ROUND(SUM(o.quantity * p.price), 2) AS spent
        FROM users u
        JOIN orders o ON o.user_id = u.id
        JOIN products p ON p.id = o.product_id
        GROUP BY u.id, u.name
        ORDER BY spent DESC;
    )"));

    std::cout << "\nDemo 3: Filtering products over 50\n";
    printTable(executeQuery(db, "SELECT id, name, price FROM products WHERE price > 50 ORDER BY price DESC;"));
}

void usage() {
    std::cout << "SQLite Database Manager\n\n"
                 "Usage:\n"
                 "  ./sqlite_manager <db-path> [options]\n"
                 "  ./sqlite_manager            # sample DB demo\n\n"
                 "Core options:\n"
                 "  --create-table <table> --columns \"id:INTEGER PRIMARY KEY,name:TEXT NOT NULL\"\n"
                 "  --insert <table> --values \"id=1,name=Alice,email=alice@example.com\"\n"
                 "  --select <table> [--columns \"id,name\"] [--where \"id > 1\"] [--limit 20]\n"
                 "  --update <table> --set \"name=Alice2\" [--where \"id=1\"]\n"
                 "  --delete <table> [--where \"id=1\"]\n"
                 "  --query \"SELECT * FROM users\"\n\n"
                 "Schema inspection:\n"
                 "  --list-tables\n"
                 "  --describe <table>\n"
                 "  --row-count <table>\n\n"
                 "Import/export:\n"
                 "  --import <file.csv> --table <table>\n"
                 "  --export <file.csv> --table <table>\n"
                 "  --export <file.csv> --query \"SELECT ...\"\n\n"
                 "Transactions:\n"
                 "  --begin\n"
                 "  --commit\n"
                 "  --rollback\n";
}

} // namespace

int main(int argc, char** argv) {
    ParsedArgs args = parseArgs(argc, argv);
    if (args.flags.count("help")) {
        usage();
        return 0;
    }

    std::filesystem::path dbPath = args.positional.empty() ? std::filesystem::absolute("sample_demo.sqlite")
                                                           : std::filesystem::absolute(args.positional[0]);
    sqlite3* db = nullptr;
    int rc = sqlite3_open(dbPath.string().c_str(), &db);
    if (rc != SQLITE_OK) {
        std::string msg = sqlite3_errmsg(db);
        if (db) sqlite3_close(db);
        std::cerr << "[Connection Error] " << msg << "\n";
        return 1;
    }

    bool mutated = !std::filesystem::exists(dbPath);
    int operations = 0;
    bool inTx = false;

    try {
        executeNonQuery(db, "PRAGMA foreign_keys = ON;");

        if (args.positional.empty()) {
            runDemo(db);
            mutated = true;
            operations++;
        } else {
            if (args.flags.count("begin")) {
                executeNonQuery(db, "BEGIN TRANSACTION;");
                inTx = true;
                std::cout << "Transaction started.\n";
            }

            if (doCreateTable(db, args)) { mutated = true; operations++; }
            if (doInsert(db, args)) { mutated = true; operations++; }
            if (doUpdate(db, args)) { mutated = true; operations++; }
            if (doDelete(db, args)) { mutated = true; operations++; }
            if (doImport(db, args)) { mutated = true; operations++; }
            if (auto q = args.options.find("query"); q != args.options.end()) {
                printTable(executeQuery(db, q->second));
                operations++;
            }
            if (doSelect(db, args)) operations++;
            if (args.flags.count("list-tables") || args.options.count("describe") || args.options.count("row-count")) {
                inspectSchema(db, args);
                operations++;
            }
            if (doExport(db, args)) operations++;

            if (inTx) {
                if (args.flags.count("rollback")) {
                    executeNonQuery(db, "ROLLBACK;");
                    std::cout << "Transaction rolled back.\n";
                    mutated = false;
                } else {
                    executeNonQuery(db, "COMMIT;");
                    if (args.flags.count("commit")) std::cout << "Transaction committed.\n";
                    else std::cout << "Transaction committed automatically.\n";
                    mutated = true;
                }
                inTx = false;
            }
        }

        if (operations == 0) usage();
        std::cout << "Database path: " << dbPath.string() << "\n";
    } catch (const std::exception& e) {
        if (inTx) {
            try { executeNonQuery(db, "ROLLBACK;"); } catch (...) {}
        }
        std::string msg = e.what();
        std::cerr << "[" << classifyError(msg) << "] " << msg << "\n";
        sqlite3_close(db);
        return 1;
    }

    sqlite3_close(db);
    return 0;
}