Files
c-relay-pg/src/db_ops_sqlite.c
T

1398 lines
47 KiB
C

#define _GNU_SOURCE
#include "sqlite_db_ops.h"
#include "debug.h"
#include "config.h"
#include <sqlite3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Database handle owned by sqlite_db_ops.
static sqlite3* g_db = NULL;
extern char g_database_path[512];
// Optional per-thread connection override (used by thread pool workers)
static __thread sqlite3* g_thread_db = NULL;
static sqlite3* sqlite_db_active_connection(void) {
return g_thread_db ? g_thread_db : g_db;
}
typedef struct sqlite_db_stmt {
sqlite3_stmt* stmt;
} sqlite_db_stmt_t;
int sqlite_db_init(const char* connection_string) {
if (!connection_string || connection_string[0] == '\0') return DB_MISUSE;
if (g_db) return DB_OK;
int rc = sqlite3_open(connection_string, &g_db);
if (rc != SQLITE_OK) {
if (g_db) {
sqlite3_close(g_db);
g_db = NULL;
}
return DB_ERROR;
}
strncpy(g_database_path, connection_string, sizeof(g_database_path) - 1);
g_database_path[sizeof(g_database_path) - 1] = '\0';
return DB_OK;
}
void sqlite_db_close(void) {
if (!g_db) return;
sqlite3_close(g_db);
g_db = NULL;
}
int sqlite_db_set_thread_connection(void* connection) {
g_thread_db = (sqlite3*)connection;
return DB_OK;
}
void sqlite_db_clear_thread_connection(void) {
g_thread_db = NULL;
}
int sqlite_db_open_worker_connection(const char* sqlite_db_path, void** out_connection) {
if (!out_connection) return -1;
*out_connection = NULL;
const char* effective_path = (sqlite_db_path && sqlite_db_path[0] != '\0') ? sqlite_db_path : g_database_path;
if (!effective_path || effective_path[0] == '\0') return -1;
sqlite3* db = NULL;
int rc = sqlite3_open_v2(effective_path, &db, SQLITE_OPEN_READWRITE, NULL);
if (rc != SQLITE_OK) {
if (db) sqlite3_close(db);
return -1;
}
sqlite3_exec(db, "PRAGMA journal_mode=WAL;", NULL, NULL, NULL);
sqlite3_exec(db, "PRAGMA wal_autocheckpoint=0;", NULL, NULL, NULL);
sqlite3_busy_timeout(db, 5000);
sqlite_db_set_thread_connection(db);
*out_connection = (void*)db;
return 0;
}
void sqlite_db_close_worker_connection(void* connection) {
if (!connection) return;
sqlite_db_clear_thread_connection();
sqlite3_close((sqlite3*)connection);
}
int sqlite_db_is_available(void) {
return sqlite_db_active_connection() != NULL;
}
const char* sqlite_db_last_error(void) {
sqlite3* db = sqlite_db_active_connection();
if (!db) return "database not available";
return sqlite3_errmsg(db);
}
const char* sqlite_db_get_database_path(void) {
return g_database_path;
}
int sqlite_db_prepare(const char* sql, sqlite_db_stmt_t** out_stmt) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !sql || !out_stmt) return DB_MISUSE;
sqlite3_stmt* raw_stmt = NULL;
int rc = sqlite3_prepare_v2(db, sql, -1, &raw_stmt, NULL);
if (rc != SQLITE_OK) return rc;
sqlite_db_stmt_t* wrapper = (sqlite_db_stmt_t*)malloc(sizeof(sqlite_db_stmt_t));
if (!wrapper) {
sqlite3_finalize(raw_stmt);
return DB_ERROR;
}
wrapper->stmt = raw_stmt;
*out_stmt = wrapper;
return DB_OK;
}
int sqlite_db_bind_text_param(sqlite_db_stmt_t* stmt, int index, const char* value) {
if (!stmt || !stmt->stmt) return DB_MISUSE;
return sqlite3_bind_text(stmt->stmt, index, value ? value : "", -1, SQLITE_TRANSIENT);
}
int sqlite_db_bind_int_param(sqlite_db_stmt_t* stmt, int index, int value) {
if (!stmt || !stmt->stmt) return DB_MISUSE;
return sqlite3_bind_int(stmt->stmt, index, value);
}
int sqlite_db_bind_int64_param(sqlite_db_stmt_t* stmt, int index, long long value) {
if (!stmt || !stmt->stmt) return DB_MISUSE;
return sqlite3_bind_int64(stmt->stmt, index, (sqlite3_int64)value);
}
int sqlite_db_step_stmt(sqlite_db_stmt_t* stmt) {
if (!stmt || !stmt->stmt) return DB_MISUSE;
return sqlite3_step(stmt->stmt);
}
int sqlite_db_reset_stmt(sqlite_db_stmt_t* stmt) {
if (!stmt || !stmt->stmt) return DB_MISUSE;
return sqlite3_reset(stmt->stmt);
}
const char* sqlite_db_column_text_value(sqlite_db_stmt_t* stmt, int col) {
if (!stmt || !stmt->stmt) return NULL;
return (const char*)sqlite3_column_text(stmt->stmt, col);
}
int sqlite_db_column_int_value(sqlite_db_stmt_t* stmt, int col) {
if (!stmt || !stmt->stmt) return 0;
return sqlite3_column_int(stmt->stmt, col);
}
long long sqlite_db_column_int64_value(sqlite_db_stmt_t* stmt, int col) {
if (!stmt || !stmt->stmt) return 0;
return (long long)sqlite3_column_int64(stmt->stmt, col);
}
double sqlite_db_column_double_value(sqlite_db_stmt_t* stmt, int col) {
if (!stmt || !stmt->stmt) return 0.0;
return sqlite3_column_double(stmt->stmt, col);
}
void sqlite_db_finalize_stmt(sqlite_db_stmt_t* stmt) {
if (!stmt) return;
if (stmt->stmt) sqlite3_finalize(stmt->stmt);
free(stmt);
}
int sqlite_db_log_subscription_created(const char* sub_id, const char* wsi_ptr,
const char* client_ip, const char* filter_json) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !sub_id || !wsi_ptr || !client_ip) return -1;
const char* sql =
"INSERT OR REPLACE INTO subscriptions (subscription_id, wsi_pointer, client_ip, event_type, filter_json) "
"VALUES (?, ?, ?, 'created', ?)";
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
sqlite3_bind_text(stmt, 1, sub_id, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, wsi_ptr, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 3, client_ip, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 4, filter_json ? filter_json : "[]", -1, SQLITE_TRANSIENT);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return (rc == SQLITE_DONE) ? 0 : -1;
}
int sqlite_db_log_subscription_closed(const char* sub_id, const char* client_ip) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !sub_id) return -1;
const char* insert_sql =
"INSERT INTO subscriptions (subscription_id, wsi_pointer, client_ip, event_type) "
"VALUES (?, '', ?, 'closed')";
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, insert_sql, -1, &stmt, NULL) == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, sub_id, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, client_ip ? client_ip : "unknown", -1, SQLITE_TRANSIENT);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
const char* update_sql =
"UPDATE subscriptions "
"SET ended_at = strftime('%s', 'now') "
"WHERE subscription_id = ? AND event_type = 'created' AND ended_at IS NULL";
if (sqlite3_prepare_v2(db, update_sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
sqlite3_bind_text(stmt, 1, sub_id, -1, SQLITE_TRANSIENT);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return (rc == SQLITE_DONE) ? 0 : -1;
}
int sqlite_db_log_subscription_disconnected(const char* client_ip) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !client_ip) return -1;
const char* update_sql =
"UPDATE subscriptions "
"SET ended_at = strftime('%s', 'now') "
"WHERE client_ip = ? AND event_type = 'created' AND ended_at IS NULL";
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, update_sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
sqlite3_bind_text(stmt, 1, client_ip, -1, SQLITE_TRANSIENT);
int rc = sqlite3_step(stmt);
int changes = sqlite3_changes(db);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) return -1;
if (changes > 0) {
const char* insert_sql =
"INSERT INTO subscriptions (subscription_id, wsi_pointer, client_ip, event_type) "
"VALUES ('disconnect', '', ?, 'disconnected')";
if (sqlite3_prepare_v2(db, insert_sql, -1, &stmt, NULL) == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, client_ip, -1, SQLITE_TRANSIENT);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
}
return changes;
}
int sqlite_db_update_subscription_events_sent(const char* sub_id, int events_sent) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !sub_id) return -1;
const char* sql =
"UPDATE subscriptions "
"SET events_sent = ? "
"WHERE subscription_id = ? AND event_type = 'created'";
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
sqlite3_bind_int(stmt, 1, events_sent);
sqlite3_bind_text(stmt, 2, sub_id, -1, SQLITE_TRANSIENT);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return (rc == SQLITE_DONE) ? 0 : -1;
}
int sqlite_db_cleanup_orphaned_subscriptions(void) {
sqlite3* db = sqlite_db_active_connection();
if (!db) return -1;
const char* sql =
"UPDATE subscriptions "
"SET ended_at = strftime('%s', 'now') "
"WHERE event_type = 'created' AND ended_at IS NULL";
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
int rc = sqlite3_step(stmt);
int changes = sqlite3_changes(db);
sqlite3_finalize(stmt);
return (rc == SQLITE_DONE) ? changes : -1;
}
int sqlite_db_get_event_pubkey(const char* event_id, char* pubkey_out, size_t pubkey_out_size) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !event_id || !pubkey_out || pubkey_out_size == 0) return -1;
const char* sql = "SELECT pubkey FROM events WHERE id = ?";
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
sqlite3_bind_text(stmt, 1, event_id, -1, SQLITE_TRANSIENT);
int rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
const char* pubkey = (const char*)sqlite3_column_text(stmt, 0);
if (pubkey) {
snprintf(pubkey_out, pubkey_out_size, "%s", pubkey);
sqlite3_finalize(stmt);
return 1;
}
}
sqlite3_finalize(stmt);
return 0;
}
int sqlite_db_delete_event_by_id(const char* event_id, const char* requester_pubkey) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !event_id || !requester_pubkey) return -1;
const char* sql = "DELETE FROM events WHERE id = ? AND pubkey = ?";
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
sqlite3_bind_text(stmt, 1, event_id, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, requester_pubkey, -1, SQLITE_TRANSIENT);
int rc = sqlite3_step(stmt);
int changes = sqlite3_changes(db);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) return -1;
return changes;
}
int sqlite_db_delete_events_by_address(const char* pubkey, int kind,
const char* d_tag, long before_timestamp) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !pubkey) return -1;
const char* sql_with_d =
"DELETE FROM events WHERE kind = ? AND pubkey = ? AND created_at <= ? "
"AND json_extract(tags, '$[*]') LIKE '%[\"d\",\"' || ? || '\"]%'";
const char* sql_no_d =
"DELETE FROM events WHERE kind = ? AND pubkey = ? AND created_at <= ?";
const int has_d = (d_tag && d_tag[0] != '\0');
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, has_d ? sql_with_d : sql_no_d, -1, &stmt, NULL) != SQLITE_OK) {
return -1;
}
sqlite3_bind_int(stmt, 1, kind);
sqlite3_bind_text(stmt, 2, pubkey, -1, SQLITE_TRANSIENT);
sqlite3_bind_int64(stmt, 3, before_timestamp);
if (has_d) {
sqlite3_bind_text(stmt, 4, d_tag, -1, SQLITE_TRANSIENT);
}
int rc = sqlite3_step(stmt);
int changes = sqlite3_changes(db);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) return -1;
return changes;
}
static int sqlite_db_scalar_exists_open(sqlite3* db, const char* sql, const char* value) {
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return 0;
if (value) sqlite3_bind_text(stmt, 1, value, -1, SQLITE_TRANSIENT);
int exists = (sqlite3_step(stmt) == SQLITE_ROW) ? 1 : 0;
sqlite3_finalize(stmt);
return exists;
}
int sqlite_db_is_pubkey_blacklisted(const char* pubkey) {
if (!pubkey || g_database_path[0] == '\0') return 0;
sqlite3* db = NULL;
if (sqlite3_open_v2(g_database_path, &db, SQLITE_OPEN_READONLY, NULL) != SQLITE_OK) return 0;
const char* sql =
"SELECT 1 FROM auth_rules WHERE rule_type = 'blacklist' "
"AND pattern_type = 'pubkey' AND pattern_value = ? AND active = 1 LIMIT 1";
int exists = sqlite_db_scalar_exists_open(db, sql, pubkey);
sqlite3_close(db);
return exists;
}
int sqlite_db_is_hash_blacklisted(const char* resource_hash) {
if (!resource_hash || g_database_path[0] == '\0') return 0;
sqlite3* db = NULL;
if (sqlite3_open_v2(g_database_path, &db, SQLITE_OPEN_READONLY, NULL) != SQLITE_OK) return 0;
const char* sql =
"SELECT 1 FROM auth_rules WHERE rule_type = 'blacklist' "
"AND pattern_type = 'hash' AND pattern_value = ? AND active = 1 LIMIT 1";
int exists = sqlite_db_scalar_exists_open(db, sql, resource_hash);
sqlite3_close(db);
return exists;
}
int sqlite_db_is_pubkey_whitelisted(const char* pubkey) {
if (!pubkey || g_database_path[0] == '\0') return 0;
sqlite3* db = NULL;
if (sqlite3_open_v2(g_database_path, &db, SQLITE_OPEN_READONLY, NULL) != SQLITE_OK) return 0;
const char* sql =
"SELECT 1 FROM auth_rules WHERE rule_type IN ('whitelist', 'wot_whitelist') "
"AND pattern_type = 'pubkey' AND pattern_value = ? AND active = 1 LIMIT 1";
int exists = sqlite_db_scalar_exists_open(db, sql, pubkey);
sqlite3_close(db);
return exists;
}
int sqlite_db_count_active_whitelist_rules(void) {
if (g_database_path[0] == '\0') return 0;
sqlite3* db = NULL;
sqlite3_stmt* stmt = NULL;
int count = 0;
if (sqlite3_open_v2(g_database_path, &db, SQLITE_OPEN_READONLY, NULL) != SQLITE_OK) return 0;
const char* sql =
"SELECT COUNT(*) FROM auth_rules WHERE rule_type IN ('whitelist', 'wot_whitelist') "
"AND pattern_type = 'pubkey' AND active = 1 LIMIT 1";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
count = sqlite3_column_int(stmt, 0);
}
sqlite3_finalize(stmt);
}
sqlite3_close(db);
return count;
}
int sqlite_db_count_with_sql(const char* sql, const char** bind_params, int bind_param_count, int* out_count) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !sql || !out_count) return -1;
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
return -1;
}
for (int i = 0; i < bind_param_count; i++) {
const char* param = (bind_params && bind_params[i]) ? bind_params[i] : "";
sqlite3_bind_text(stmt, i + 1, param, -1, SQLITE_TRANSIENT);
}
int count = 0;
if (sqlite3_step(stmt) == SQLITE_ROW) {
count = sqlite3_column_int(stmt, 0);
}
sqlite3_finalize(stmt);
*out_count = count;
return 0;
}
char* sqlite_db_execute_readonly_query_json(const char* query, const char* request_id,
char* error_message, size_t error_size,
int max_rows, int timeout_ms) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !query || !request_id || !error_message) return NULL;
sqlite3_busy_timeout(db, timeout_ms > 0 ? timeout_ms : 5000);
sqlite3_stmt* stmt = NULL;
int rc = sqlite3_prepare_v2(db, query, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
const char* err_msg = sqlite3_errmsg(db);
snprintf(error_message, error_size, "SQL prepare failed: %s", err_msg ? err_msg : "unknown");
return NULL;
}
cJSON* response = cJSON_CreateObject();
if (!response) {
sqlite3_finalize(stmt);
snprintf(error_message, error_size, "Failed to allocate query response object");
return NULL;
}
cJSON_AddStringToObject(response, "query_type", "sql_query");
cJSON_AddStringToObject(response, "request_id", request_id);
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
cJSON_AddStringToObject(response, "query", query);
int col_count = sqlite3_column_count(stmt);
cJSON* columns = cJSON_CreateArray();
if (!columns) {
sqlite3_finalize(stmt);
cJSON_Delete(response);
snprintf(error_message, error_size, "Failed to allocate columns array");
return NULL;
}
for (int i = 0; i < col_count; i++) {
const char* col_name = sqlite3_column_name(stmt, i);
cJSON_AddItemToArray(columns, cJSON_CreateString(col_name ? col_name : ""));
}
cJSON_AddItemToObject(response, "columns", columns);
cJSON* rows = cJSON_CreateArray();
if (!rows) {
sqlite3_finalize(stmt);
cJSON_Delete(response);
snprintf(error_message, error_size, "Failed to allocate rows array");
return NULL;
}
const int row_limit = (max_rows > 0) ? max_rows : 1000;
int row_count = 0;
struct timespec start_time;
clock_gettime(CLOCK_MONOTONIC, &start_time);
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW && row_count < row_limit) {
cJSON* row = cJSON_CreateArray();
if (!row) {
sqlite3_finalize(stmt);
cJSON_Delete(rows);
cJSON_Delete(response);
snprintf(error_message, error_size, "Failed to allocate row array");
return NULL;
}
for (int i = 0; i < col_count; i++) {
int col_type = sqlite3_column_type(stmt, i);
switch (col_type) {
case SQLITE_INTEGER:
cJSON_AddItemToArray(row, cJSON_CreateNumber((double)sqlite3_column_int64(stmt, i)));
break;
case SQLITE_FLOAT:
cJSON_AddItemToArray(row, cJSON_CreateNumber(sqlite3_column_double(stmt, i)));
break;
case SQLITE_TEXT: {
const char* text = (const char*)sqlite3_column_text(stmt, i);
cJSON_AddItemToArray(row, cJSON_CreateString(text ? text : ""));
break;
}
case SQLITE_BLOB: {
const void* blob = sqlite3_column_blob(stmt, i);
int blob_size = sqlite3_column_bytes(stmt, i);
if (blob && blob_size > 0) {
char* hex_str = malloc((size_t)blob_size * 2 + 1);
if (hex_str) {
for (int j = 0; j < blob_size; j++) {
sprintf(hex_str + j * 2, "%02x", ((const unsigned char*)blob)[j]);
}
hex_str[(size_t)blob_size * 2] = '\0';
cJSON_AddItemToArray(row, cJSON_CreateString(hex_str));
free(hex_str);
} else {
cJSON_AddItemToArray(row, cJSON_CreateString("[BLOB]"));
}
} else {
cJSON_AddItemToArray(row, cJSON_CreateString(""));
}
break;
}
case SQLITE_NULL:
cJSON_AddItemToArray(row, cJSON_CreateNull());
break;
default:
cJSON_AddItemToArray(row, cJSON_CreateString("[UNKNOWN]"));
break;
}
}
cJSON_AddItemToArray(rows, row);
row_count++;
struct timespec current_time;
clock_gettime(CLOCK_MONOTONIC, &current_time);
double elapsed = (current_time.tv_sec - start_time.tv_sec) +
(current_time.tv_nsec - start_time.tv_nsec) / 1e9;
if (elapsed > 4.5) {
break;
}
}
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE && rc != SQLITE_ROW) {
const char* err_msg = sqlite3_errmsg(db);
snprintf(error_message, error_size, "SQL execution failed: %s", err_msg ? err_msg : "unknown");
cJSON_Delete(rows);
cJSON_Delete(response);
return NULL;
}
if (row_count >= row_limit) {
cJSON_AddStringToObject(response, "warning", "Result truncated to maximum row limit");
}
cJSON_AddNumberToObject(response, "row_count", row_count);
cJSON_AddNumberToObject(response, "execution_time_ms", 0);
cJSON_AddItemToObject(response, "rows", rows);
char* json_result = cJSON_Print(response);
cJSON_Delete(response);
if (!json_result) {
snprintf(error_message, error_size, "Failed to generate JSON response");
return NULL;
}
return json_result;
}
int sqlite_db_get_total_event_count_ll(long long* out_count) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !out_count) return -1;
sqlite3_stmt* stmt = NULL;
const char* sql = "SELECT COUNT(*) FROM events";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
long long count = 0;
if (sqlite3_step(stmt) == SQLITE_ROW) {
count = sqlite3_column_int64(stmt, 0);
}
sqlite3_finalize(stmt);
*out_count = count;
return 0;
}
int sqlite_db_get_event_count_since(time_t cutoff, long long* out_count) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !out_count) return -1;
sqlite3_stmt* stmt = NULL;
const char* sql = "SELECT COUNT(*) FROM events WHERE created_at >= ?";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)cutoff);
long long count = 0;
if (sqlite3_step(stmt) == SQLITE_ROW) {
count = sqlite3_column_int64(stmt, 0);
}
sqlite3_finalize(stmt);
*out_count = count;
return 0;
}
cJSON* sqlite_db_get_event_kind_distribution_rows(long long* out_total_events) {
sqlite3* db = sqlite_db_active_connection();
if (!db) return NULL;
sqlite3_stmt* stmt = NULL;
const char* sql = "SELECT kind, COUNT(*) as count FROM events GROUP BY kind ORDER BY count DESC";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return NULL;
cJSON* rows = cJSON_CreateArray();
if (!rows) {
sqlite3_finalize(stmt);
return NULL;
}
long long total = 0;
while (sqlite3_step(stmt) == SQLITE_ROW) {
cJSON* row = cJSON_CreateObject();
if (!row) {
sqlite3_finalize(stmt);
cJSON_Delete(rows);
return NULL;
}
int kind = sqlite3_column_int(stmt, 0);
long long count = sqlite3_column_int64(stmt, 1);
total += count;
cJSON_AddNumberToObject(row, "kind", kind);
cJSON_AddNumberToObject(row, "count", count);
cJSON_AddItemToArray(rows, row);
}
sqlite3_finalize(stmt);
if (out_total_events) {
*out_total_events = total;
}
return rows;
}
cJSON* sqlite_db_get_top_pubkeys_rows(int limit) {
sqlite3* db = sqlite_db_active_connection();
if (!db) return NULL;
sqlite3_stmt* stmt = NULL;
const char* sql = "SELECT pubkey, COUNT(*) as count FROM events GROUP BY pubkey ORDER BY count DESC LIMIT ?";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return NULL;
sqlite3_bind_int(stmt, 1, limit > 0 ? limit : 10);
cJSON* rows = cJSON_CreateArray();
if (!rows) {
sqlite3_finalize(stmt);
return NULL;
}
while (sqlite3_step(stmt) == SQLITE_ROW) {
cJSON* row = cJSON_CreateObject();
if (!row) {
sqlite3_finalize(stmt);
cJSON_Delete(rows);
return NULL;
}
const char* pubkey = (const char*)sqlite3_column_text(stmt, 0);
long long count = sqlite3_column_int64(stmt, 1);
cJSON_AddStringToObject(row, "pubkey", pubkey ? pubkey : "");
cJSON_AddNumberToObject(row, "count", count);
cJSON_AddItemToArray(rows, row);
}
sqlite3_finalize(stmt);
return rows;
}
cJSON* sqlite_db_get_subscription_details_rows(void) {
sqlite3* db = sqlite_db_active_connection();
if (!db) return NULL;
sqlite3_stmt* stmt = NULL;
const char* sql =
"SELECT * "
"FROM active_subscriptions_log "
"ORDER BY created_at DESC";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return NULL;
cJSON* rows = cJSON_CreateArray();
if (!rows) {
sqlite3_finalize(stmt);
return NULL;
}
while (sqlite3_step(stmt) == SQLITE_ROW) {
cJSON* row = cJSON_CreateObject();
if (!row) {
sqlite3_finalize(stmt);
cJSON_Delete(rows);
return NULL;
}
const char* sub_id = (const char*)sqlite3_column_text(stmt, 0);
const char* client_ip = (const char*)sqlite3_column_text(stmt, 1);
const char* filter_json = (const char*)sqlite3_column_text(stmt, 2);
long long events_sent = sqlite3_column_int64(stmt, 3);
long long created_at = sqlite3_column_int64(stmt, 4);
long long duration_seconds = sqlite3_column_int64(stmt, 5);
const char* wsi_pointer = (const char*)sqlite3_column_text(stmt, 6);
cJSON_AddStringToObject(row, "id", sub_id ? sub_id : "");
cJSON_AddStringToObject(row, "client_ip", client_ip ? client_ip : "");
cJSON_AddStringToObject(row, "filter_json", filter_json ? filter_json : "[]");
cJSON_AddNumberToObject(row, "events_sent", events_sent);
cJSON_AddNumberToObject(row, "created_at", created_at);
cJSON_AddNumberToObject(row, "duration_seconds", duration_seconds);
cJSON_AddStringToObject(row, "wsi_pointer", wsi_pointer ? wsi_pointer : "");
cJSON_AddItemToArray(rows, row);
}
sqlite3_finalize(stmt);
return rows;
}
cJSON* sqlite_db_get_all_config_rows(void) {
sqlite3* db = sqlite_db_active_connection();
if (!db) return NULL;
sqlite3_stmt* stmt = NULL;
const char* sql = "SELECT key, value FROM config ORDER BY key";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return NULL;
cJSON* rows = cJSON_CreateArray();
if (!rows) {
sqlite3_finalize(stmt);
return NULL;
}
while (sqlite3_step(stmt) == SQLITE_ROW) {
cJSON* row = cJSON_CreateObject();
if (!row) {
sqlite3_finalize(stmt);
cJSON_Delete(rows);
return NULL;
}
const char* key = (const char*)sqlite3_column_text(stmt, 0);
const char* value = (const char*)sqlite3_column_text(stmt, 1);
cJSON_AddStringToObject(row, "key", key ? key : "");
cJSON_AddStringToObject(row, "value", value ? value : "");
cJSON_AddItemToArray(rows, row);
}
sqlite3_finalize(stmt);
return rows;
}
char* sqlite_db_get_config_value_dup(const char* key) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !key) return NULL;
sqlite3_stmt* stmt = NULL;
const char* sql = "SELECT value FROM config WHERE key = ?";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return NULL;
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
char* result = NULL;
if (sqlite3_step(stmt) == SQLITE_ROW) {
const char* value = (const char*)sqlite3_column_text(stmt, 0);
if (value) result = strdup(value);
}
sqlite3_finalize(stmt);
return result;
}
int sqlite_db_set_config_value_full(const char* key, const char* value, const char* data_type,
const char* description, const char* category, int requires_restart) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !key || !value || !data_type) return -1;
sqlite3_stmt* stmt = NULL;
const char* sql =
"INSERT OR REPLACE INTO config (key, value, data_type, description, category, requires_restart) "
"VALUES (?, ?, ?, ?, ?, ?)";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
return -1;
}
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, value, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 3, data_type, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 4, description ? description : "", -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 5, category ? category : "general", -1, SQLITE_TRANSIENT);
sqlite3_bind_int(stmt, 6, requires_restart);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return (rc == SQLITE_DONE) ? 0 : -1;
}
int sqlite_db_update_config_value_only(const char* key, const char* value) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !key || !value) return -1;
sqlite3_stmt* stmt = NULL;
const char* sql = "UPDATE config SET value = ?, updated_at = strftime('%s', 'now') WHERE key = ?";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
return -1;
}
sqlite3_bind_text(stmt, 1, value, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, key, -1, SQLITE_TRANSIENT);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return (rc == SQLITE_DONE) ? 0 : -1;
}
int sqlite_db_upsert_config_value(const char* key, const char* value, const char* data_type) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !key || !value || !data_type) return -1;
sqlite3_stmt* stmt = NULL;
const char* sql = "INSERT OR REPLACE INTO config (key, value, data_type) VALUES (?, ?, ?)";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
return -1;
}
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, value, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 3, data_type, -1, SQLITE_TRANSIENT);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return (rc == SQLITE_DONE) ? 0 : -1;
}
int sqlite_db_store_relay_private_key_hex(const char* relay_privkey_hex) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !relay_privkey_hex) return -1;
sqlite3_stmt* stmt = NULL;
const char* sql = "INSERT OR REPLACE INTO relay_seckey (private_key_hex) VALUES (?)";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
sqlite3_bind_text(stmt, 1, relay_privkey_hex, -1, SQLITE_TRANSIENT);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return (rc == SQLITE_DONE) ? 0 : -1;
}
char* sqlite_db_get_relay_private_key_hex_dup(void) {
sqlite3* db = sqlite_db_active_connection();
if (!db) return NULL;
sqlite3_stmt* stmt = NULL;
const char* sql = "SELECT private_key_hex FROM relay_seckey";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return NULL;
char* result = NULL;
if (sqlite3_step(stmt) == SQLITE_ROW) {
const char* key_from_db = (const char*)sqlite3_column_text(stmt, 0);
if (key_from_db) result = strdup(key_from_db);
}
sqlite3_finalize(stmt);
return result;
}
int sqlite_db_store_config_event(const cJSON* event) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !event) return -1;
cJSON* id_obj = cJSON_GetObjectItemCaseSensitive((cJSON*)event, "id");
cJSON* pubkey_obj = cJSON_GetObjectItemCaseSensitive((cJSON*)event, "pubkey");
cJSON* created_at_obj = cJSON_GetObjectItemCaseSensitive((cJSON*)event, "created_at");
cJSON* kind_obj = cJSON_GetObjectItemCaseSensitive((cJSON*)event, "kind");
cJSON* content_obj = cJSON_GetObjectItemCaseSensitive((cJSON*)event, "content");
cJSON* sig_obj = cJSON_GetObjectItemCaseSensitive((cJSON*)event, "sig");
cJSON* tags_obj = cJSON_GetObjectItemCaseSensitive((cJSON*)event, "tags");
if (!id_obj || !pubkey_obj || !created_at_obj || !kind_obj || !content_obj || !sig_obj || !tags_obj) {
return -1;
}
char* tags_str = cJSON_Print(tags_obj);
if (!tags_str) return -1;
sqlite3_stmt* stmt = NULL;
const char* sql = "INSERT OR REPLACE INTO events (id, pubkey, created_at, kind, event_type, content, sig, tags) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
free(tags_str);
return -1;
}
sqlite3_bind_text(stmt, 1, cJSON_GetStringValue(id_obj), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, cJSON_GetStringValue(pubkey_obj), -1, SQLITE_TRANSIENT);
sqlite3_bind_int64(stmt, 3, (sqlite3_int64)cJSON_GetNumberValue(created_at_obj));
sqlite3_bind_int(stmt, 4, (int)cJSON_GetNumberValue(kind_obj));
sqlite3_bind_text(stmt, 5, "regular", -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 6, cJSON_GetStringValue(content_obj), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 7, cJSON_GetStringValue(sig_obj), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 8, tags_str, -1, SQLITE_TRANSIENT);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
free(tags_str);
return (rc == SQLITE_DONE) ? 0 : -1;
}
static int sqlite_db_insert_event_tags_json_with_db(sqlite3* db, const char* event_id, const char* tags_json) {
if (!db || !event_id || !tags_json) {
return -1;
}
cJSON* tags = cJSON_Parse(tags_json);
if (!tags || !cJSON_IsArray(tags)) {
if (tags) cJSON_Delete(tags);
return 0;
}
const char* sql = "INSERT INTO event_tags (event_id, tag_name, tag_value, tag_index) VALUES (?, ?, ?, ?)";
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
cJSON_Delete(tags);
return -1;
}
int tag_index = 0;
cJSON* tag = NULL;
cJSON_ArrayForEach(tag, tags) {
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
cJSON* name = cJSON_GetArrayItem(tag, 0);
cJSON* value = cJSON_GetArrayItem(tag, 1);
if (cJSON_IsString(name) && cJSON_IsString(value)) {
sqlite3_reset(stmt);
sqlite3_clear_bindings(stmt);
sqlite3_bind_text(stmt, 1, event_id, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, cJSON_GetStringValue(name), -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, cJSON_GetStringValue(value), -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 4, tag_index);
if (sqlite3_step(stmt) != SQLITE_DONE) {
DEBUG_WARN("Failed to insert event tag for %s: %s", event_id, sqlite3_errmsg(db));
}
}
}
tag_index++;
}
sqlite3_finalize(stmt);
cJSON_Delete(tags);
return 0;
}
int sqlite_db_insert_event_with_json(const char* id, const char* pubkey, long long created_at,
int kind, const char* event_type, const char* content,
const char* sig, const char* tags_json, const char* event_json,
int* out_step_rc, int* out_extended_errcode) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !id || !pubkey || !event_type || !content || !sig || !tags_json || !event_json) {
return -1;
}
const char* sql =
"INSERT INTO events (id, pubkey, created_at, kind, event_type, content, sig, tags, event_json) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
return -1;
}
sqlite3_bind_text(stmt, 1, id, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, pubkey, -1, SQLITE_TRANSIENT);
sqlite3_bind_int64(stmt, 3, (sqlite3_int64)created_at);
sqlite3_bind_int(stmt, 4, kind);
sqlite3_bind_text(stmt, 5, event_type, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 6, content, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 7, sig, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 8, tags_json, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 9, event_json, -1, SQLITE_TRANSIENT);
int step_rc = sqlite3_step(stmt);
int extended_errcode = sqlite3_extended_errcode(db);
sqlite3_finalize(stmt);
if (step_rc == SQLITE_DONE) {
(void)sqlite_db_insert_event_tags_json_with_db(db, id, tags_json);
}
if (out_step_rc) {
*out_step_rc = step_rc;
}
if (out_extended_errcode) {
*out_extended_errcode = extended_errcode;
}
return 0;
}
int sqlite_db_get_event_time_bounds(long long* out_min_created_at, long long* out_max_created_at) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !out_min_created_at || !out_max_created_at) return -1;
sqlite3_stmt* stmt = NULL;
const char* sql = "SELECT MIN(created_at), MAX(created_at) FROM events";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
return -1;
}
long long min_ts = 0;
long long max_ts = 0;
if (sqlite3_step(stmt) == SQLITE_ROW) {
min_ts = sqlite3_column_int64(stmt, 0);
max_ts = sqlite3_column_int64(stmt, 1);
}
sqlite3_finalize(stmt);
*out_min_created_at = min_ts;
*out_max_created_at = max_ts;
return 0;
}
int sqlite_db_event_id_exists(const char* event_id, int* out_exists) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !event_id || !out_exists) return -1;
sqlite3_stmt* stmt = NULL;
const char* sql = "SELECT 1 FROM events WHERE id=? LIMIT 1";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
sqlite3_bind_text(stmt, 1, event_id, 64, SQLITE_STATIC);
int exists = (sqlite3_step(stmt) == SQLITE_ROW) ? 1 : 0;
sqlite3_finalize(stmt);
*out_exists = exists;
return 0;
}
cJSON* sqlite_db_retrieve_event_by_id(const char* event_id) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !event_id) return NULL;
const char* sql =
"SELECT id, pubkey, created_at, kind, content, sig, tags FROM events WHERE id = ?";
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
return NULL;
}
sqlite3_bind_text(stmt, 1, event_id, -1, SQLITE_STATIC);
cJSON* event = NULL;
if (sqlite3_step(stmt) == SQLITE_ROW) {
event = cJSON_CreateObject();
if (event) {
cJSON_AddStringToObject(event, "id", (const char*)sqlite3_column_text(stmt, 0));
cJSON_AddStringToObject(event, "pubkey", (const char*)sqlite3_column_text(stmt, 1));
cJSON_AddNumberToObject(event, "created_at", sqlite3_column_int64(stmt, 2));
cJSON_AddNumberToObject(event, "kind", sqlite3_column_int(stmt, 3));
cJSON_AddStringToObject(event, "content", (const char*)sqlite3_column_text(stmt, 4));
cJSON_AddStringToObject(event, "sig", (const char*)sqlite3_column_text(stmt, 5));
const char* tags_json = (const char*)sqlite3_column_text(stmt, 6);
if (tags_json) {
cJSON* tags = cJSON_Parse(tags_json);
if (tags) {
cJSON_AddItemToObject(event, "tags", tags);
} else {
cJSON_AddItemToObject(event, "tags", cJSON_CreateArray());
}
} else {
cJSON_AddItemToObject(event, "tags", cJSON_CreateArray());
}
}
}
sqlite3_finalize(stmt);
return event;
}
char* sqlite_db_get_latest_event_pubkey_for_kind_dup(int kind) {
sqlite3* db = sqlite_db_active_connection();
if (!db) return NULL;
const char* sql = "SELECT pubkey FROM events WHERE kind = ? ORDER BY created_at DESC LIMIT 1";
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
return NULL;
}
sqlite3_bind_int(stmt, 1, kind);
char* pubkey_dup = NULL;
if (sqlite3_step(stmt) == SQLITE_ROW) {
const char* pubkey = (const char*)sqlite3_column_text(stmt, 0);
if (pubkey) {
pubkey_dup = strdup(pubkey);
}
}
sqlite3_finalize(stmt);
return pubkey_dup;
}
int sqlite_db_get_config_row_count(int* out_count) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !out_count) return -1;
sqlite3_stmt* stmt = NULL;
const char* sql = "SELECT COUNT(*) FROM config";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
return -1;
}
int count = 0;
if (sqlite3_step(stmt) == SQLITE_ROW) {
count = sqlite3_column_int(stmt, 0);
}
sqlite3_finalize(stmt);
*out_count = count;
return 0;
}
int sqlite_db_store_event_tags_cjson(const char* event_id, const cJSON* tags) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !event_id || !tags || !cJSON_IsArray(tags)) {
return 0; // Not an error if no tags
}
const char* sql = "INSERT INTO event_tags (event_id, tag_name, tag_value, tag_index) VALUES (?, ?, ?, ?)";
sqlite3_stmt* stmt = NULL;
int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
DEBUG_ERROR("Failed to prepare event_tags insert: %s", sqlite3_errmsg(db));
return -1;
}
int tag_index = 0;
cJSON* tag = NULL;
cJSON_ArrayForEach(tag, tags) {
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
cJSON* name = cJSON_GetArrayItem(tag, 0);
cJSON* value = cJSON_GetArrayItem(tag, 1);
if (cJSON_IsString(name) && cJSON_IsString(value)) {
sqlite3_reset(stmt);
sqlite3_bind_text(stmt, 1, event_id, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, cJSON_GetStringValue(name), -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, cJSON_GetStringValue(value), -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 4, tag_index);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
DEBUG_ERROR("Failed to insert event tag: %s", sqlite3_errmsg(db));
}
}
}
tag_index++;
}
sqlite3_finalize(stmt);
return 0;
}
int sqlite_db_populate_event_tags_from_existing(void) {
sqlite3* db = sqlite_db_active_connection();
if (!db) return -1;
sqlite3_stmt* check_stmt = NULL;
if (sqlite3_prepare_v2(db, "SELECT COUNT(*) FROM event_tags", -1, &check_stmt, NULL) != SQLITE_OK) {
return -1;
}
if (sqlite3_step(check_stmt) == SQLITE_ROW && sqlite3_column_int(check_stmt, 0) > 0) {
sqlite3_finalize(check_stmt);
DEBUG_INFO("event_tags already populated, skipping");
return 0;
}
sqlite3_finalize(check_stmt);
DEBUG_INFO("Populating event_tags from existing events...");
const char* sql = "SELECT id, tags FROM events WHERE tags != '[]'";
sqlite3_stmt* stmt = NULL;
int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) return -1;
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL);
int event_count = 0;
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char* event_id = (const char*)sqlite3_column_text(stmt, 0);
const char* tags_json = (const char*)sqlite3_column_text(stmt, 1);
if (event_id && tags_json) {
cJSON* tags = cJSON_Parse(tags_json);
if (tags) {
sqlite_db_store_event_tags_cjson(event_id, tags);
cJSON_Delete(tags);
event_count++;
}
}
}
sqlite3_finalize(stmt);
sqlite3_exec(db, "COMMIT", NULL, NULL, NULL);
DEBUG_INFO("Populated event_tags for %d events", event_count);
return 0;
}
int sqlite_db_add_auth_rule(const char* rule_type, const char* pattern_type, const char* pattern_value) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !rule_type || !pattern_type || !pattern_value) return -1;
sqlite3_stmt* stmt = NULL;
const char* sql = "INSERT INTO auth_rules (rule_type, pattern_type, pattern_value) VALUES (?, ?, ?)";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
sqlite3_bind_text(stmt, 1, rule_type, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, pattern_type, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 3, pattern_value, -1, SQLITE_TRANSIENT);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return (rc == SQLITE_DONE) ? 0 : -1;
}
int sqlite_db_remove_auth_rule(const char* rule_type, const char* pattern_type, const char* pattern_value) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !rule_type || !pattern_type || !pattern_value) return -1;
sqlite3_stmt* stmt = NULL;
const char* sql = "DELETE FROM auth_rules WHERE rule_type = ? AND pattern_type = ? AND pattern_value = ?";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
sqlite3_bind_text(stmt, 1, rule_type, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, pattern_type, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 3, pattern_value, -1, SQLITE_TRANSIENT);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return (rc == SQLITE_DONE) ? 0 : -1;
}
int sqlite_db_delete_wot_whitelist_rules(void) {
sqlite3* db = sqlite_db_active_connection();
if (!db) return -1;
const char* sql = "DELETE FROM auth_rules WHERE rule_type = 'wot_whitelist'";
int rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
return (rc == SQLITE_OK) ? 0 : -1;
}
int sqlite_db_count_wot_whitelist_rules(void) {
if (!sqlite_db_is_available()) return 0;
const char* sql = "SELECT COUNT(*) FROM auth_rules WHERE rule_type = 'wot_whitelist' AND active = 1";
int count = 0;
if (sqlite_db_count_with_sql(sql, NULL, 0, &count) != 0) {
return 0;
}
return count;
}
int sqlite_db_table_exists(const char* table_name, int* out_exists) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !table_name || !out_exists) return -1;
const char* sql = "SELECT 1 FROM sqlite_master WHERE type='table' AND name=? LIMIT 1";
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
return -1;
}
sqlite3_bind_text(stmt, 1, table_name, -1, SQLITE_TRANSIENT);
*out_exists = (sqlite3_step(stmt) == SQLITE_ROW) ? 1 : 0;
sqlite3_finalize(stmt);
return 0;
}
char* sqlite_db_get_schema_version_dup(void) {
sqlite3* db = sqlite_db_active_connection();
if (!db) return NULL;
const char* sql = "SELECT value FROM schema_info WHERE key = 'version'";
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
return NULL;
}
char* result = NULL;
if (sqlite3_step(stmt) == SQLITE_ROW) {
const char* version = (const char*)sqlite3_column_text(stmt, 0);
if (version) {
result = strdup(version);
}
}
sqlite3_finalize(stmt);
return result;
}
int sqlite_db_exec_sql(const char* sql) {
sqlite3* db = sqlite_db_active_connection();
if (!db || !sql) return -1;
int rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
return (rc == SQLITE_OK) ? 0 : -1;
}
int sqlite_db_wal_checkpoint_passive(void) {
sqlite3* db = sqlite_db_active_connection();
if (!db) return -1;
int rc = sqlite3_wal_checkpoint_v2(db, NULL, SQLITE_CHECKPOINT_PASSIVE, NULL, NULL);
return (rc == SQLITE_OK) ? 0 : -1;
}
int sqlite_db_wal_checkpoint_truncate(void) {
sqlite3* db = sqlite_db_active_connection();
if (!db) return -1;
int rc = sqlite3_wal_checkpoint_v2(db, NULL, SQLITE_CHECKPOINT_TRUNCATE, NULL, NULL);
return (rc == SQLITE_OK) ? 0 : -1;
}