diff --git a/CMakeLists.txt b/CMakeLists.txt
index b657983..0e5527f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -145,7 +145,7 @@ else()
include(FetchContent)
FetchContent_Declare(libxsql
GIT_REPOSITORY https://github.com/0xeb/libxsql.git
- GIT_TAG main
+ GIT_TAG v1.0.9
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(libxsql)
diff --git a/README.md b/README.md
index 3194c6b..ce74be6 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,7 @@
+
+
+
+
# BNSQL
> **Status:** Pre-release Alpha | **Platform:** Windows only (for now)
diff --git a/assets/logo.jpg b/assets/logo.jpg
new file mode 100644
index 0000000..df9584b
Binary files /dev/null and b/assets/logo.jpg differ
diff --git a/src/cli/main.cpp b/src/cli/main.cpp
index 9cab7e4..f7394a5 100644
--- a/src/cli/main.cpp
+++ b/src/cli/main.cpp
@@ -40,6 +40,7 @@
#ifdef BNSQL_HAS_HTTP
#include
+#include
#include "bnsql_http_routes.hpp"
#endif
@@ -148,7 +149,7 @@ static void quit_signal_handler(int) {
// ============================================================================
#ifdef BNSQL_HAS_HTTP
-static xsql::thinclient::server* g_http_server = nullptr;
+static xsql::thinclient::http_query_server* g_http_server = nullptr;
static std::unique_ptr g_repl_http_server;
static void http_signal_handler(int) {
@@ -212,54 +213,97 @@ static void setup_http_callbacks(bnsql::CommandCallbacks& callbacks, bnsql::Quer
}
static int run_http_mode(bnsql::QueryEngine& qe, int port, const std::string& bind_addr, const std::string& auth_token) {
- xsql::thinclient::server_config cfg;
- cfg.port = port;
- cfg.bind_address = bind_addr.empty() ? "127.0.0.1" : bind_addr;
- if (!auth_token.empty()) {
- cfg.auth_token = auth_token;
- }
- if (!bind_addr.empty() && bind_addr != "127.0.0.1" && bind_addr != "localhost") {
- cfg.allow_insecure_no_auth = auth_token.empty();
+ if (!bind_addr.empty() && bind_addr != "127.0.0.1" && bind_addr != "localhost"
+ && auth_token.empty()) {
std::cerr << "WARNING: Binding to non-loopback address " << bind_addr << "\n";
- if (auth_token.empty()) {
- std::cerr << "WARNING: No authentication token set. Server is accessible without authentication.\n";
- std::cerr << " Consider using --token for remote access.\n";
- }
+ std::cerr << "WARNING: No authentication token set. Server is accessible without authentication.\n";
+ std::cerr << " Consider using --token for remote access.\n";
}
- cfg.setup_routes = [&qe, &auth_token, port](httplib::Server& svr) {
- bnsql::setup_http_routes(svr,
- [&qe](const std::string& sql) { return qe.query(sql); },
- auth_token, port);
+ xsql::thinclient::http_query_server_config cfg;
+ cfg.tool_name = "bnsql";
+ cfg.help_text = bnsql::http_help_text();
+ cfg.port = port;
+ cfg.bind_address = bind_addr.empty() ? "127.0.0.1" : bind_addr;
+ if (!auth_token.empty()) cfg.auth_token = auth_token;
+ // BN's QueryEngine is not concurrency-safe; serialize on the main thread
+ // via the thinclient queue (also provides admission timeout + max_queue).
+ cfg.use_queue = true;
+ cfg.queue_admission_timeout_ms_fn = []() {
+ return bnsql::runtime_settings().queue_admission_timeout_ms();
+ };
+ cfg.max_queue_fn = []() { return bnsql::runtime_settings().max_queue(); };
+ cfg.statement_executor = [&qe](const std::string& stmt, xsql::ScriptStatementResult& out) {
+ auto r = qe.query(stmt);
+ out.columns = r.columns;
+ out.rows.reserve(r.rows.size());
+ for (const auto& row : r.rows) out.rows.push_back(row.values);
+ out.elapsed_ms = r.elapsed_ms;
+ out.success = r.success;
+ out.error = r.error;
+ };
+
+ // Function count is static after load; query once (main thread) and report
+ // the cached value from status_fn (which runs off the main thread).
+ std::string func_count;
+ {
+ auto r = qe.query("SELECT COUNT(*) FROM funcs");
+ if (r.success && !r.empty()) func_count = r.rows[0][0];
+ }
+ cfg.status_fn = [func_count]() -> xsql::json {
+ xsql::json j;
+ j["tool"] = "bnsql";
+ j["functions"] = func_count.empty() ? xsql::json(nullptr)
+ : xsql::json(std::stoll(func_count));
+ j["settings"] = xsql::json::parse(
+ bnsql::settings_to_json(bnsql::runtime_settings().snapshot()));
+ return j;
+ };
+ // Preserve bnsql's auth-gated GET /settings (thinclient extra routes are raw).
+ cfg.extra_routes = [auth_token](httplib::Server& svr) {
+ svr.Get("/settings", [auth_token](const httplib::Request& req, httplib::Response& res) {
+ if (!auth_token.empty()) {
+ std::string tok;
+ if (req.has_header("X-XSQL-Token")) tok = req.get_header_value("X-XSQL-Token");
+ else if (req.has_header("Authorization")) {
+ auto a = req.get_header_value("Authorization");
+ if (a.rfind("Bearer ", 0) == 0) tok = a.substr(7);
+ }
+ if (tok != auth_token) {
+ res.status = 401;
+ res.set_content("{\"success\":false,\"error\":\"Unauthorized\"}", "application/json");
+ return;
+ }
+ }
+ res.set_content(bnsql::settings_to_json(bnsql::runtime_settings().snapshot()),
+ "application/json");
+ });
};
- xsql::thinclient::server http_server(cfg);
- g_http_server = &http_server;
+ auto server = std::make_unique(cfg);
+ g_http_server = server.get();
auto old_handler = std::signal(SIGINT, http_signal_handler);
#ifdef _WIN32
std::signal(SIGBREAK, http_signal_handler);
#endif
- http_server.run_async();
- int actual_port = http_server.port();
+ int actual_port = server->start();
+ if (actual_port < 0) {
+ std::cerr << "Error: Failed to start HTTP server\n";
+ g_http_server = nullptr;
+ return 1;
+ }
std::cout << "HTTP server listening on http://" << cfg.bind_address << ":" << actual_port << "\n";
- std::cout << "Endpoints:\n";
- std::cout << " GET /help - API documentation\n";
- std::cout << " POST /query - Execute SQL (body = raw SQL)\n";
- std::cout << " GET /status - Health check\n";
- std::cout << " POST /shutdown - Stop server\n";
- std::cout << "\nExamples:\n";
- std::cout << " curl http://localhost:" << actual_port << "/help\n";
+ std::cout << "Endpoints: /help /query /status /settings /shutdown\n";
std::cout << " curl -X POST http://localhost:" << actual_port << "/query -d \"SELECT name FROM funcs LIMIT 5\"\n";
std::cout << "\nPress Ctrl+C to stop.\n\n";
std::cout.flush();
- // Block until server stops (via signal or /shutdown)
- while (http_server.is_running()) {
- std::this_thread::sleep_for(std::chrono::milliseconds(100));
- }
+ // Process the query queue on this (main) thread until stopped.
+ server->run_until_stopped();
+ server->stop();
std::signal(SIGINT, old_handler);
g_http_server = nullptr;