2026-03-04 21:08:06 -07:00
|
|
|
/*! \file sqlitedb.cpp
|
2026-03-04 21:23:24 -07:00
|
|
|
* \brief SQLite storage backend for TinyMUX.
|
2026-03-04 21:08:06 -07:00
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|
2026-03-04 21:23:24 -07:00
|
|
|
#if !defined(TINYMUX_TYPES_DEFINED)
|
2026-03-04 21:08:06 -07:00
|
|
|
#include "copyright.h"
|
|
|
|
|
#include "autoconf.h"
|
|
|
|
|
#include "config.h"
|
2026-03-04 21:23:24 -07:00
|
|
|
#include "externs.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#include "sqlitedb.h"
|
2026-03-20 21:11:02 -06:00
|
|
|
#include "engine_api.h"
|
2026-03-04 21:23:24 -07:00
|
|
|
#include <cstring>
|
|
|
|
|
#include <cstdio>
|
2026-04-01 20:08:41 -06:00
|
|
|
#include <ctime>
|
2026-03-05 08:58:32 -07:00
|
|
|
#include <string>
|
2026-03-04 21:23:24 -07:00
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Construction / Destruction
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
CSQLiteDB::CSQLiteDB()
|
|
|
|
|
: m_db(nullptr),
|
|
|
|
|
m_stmtObjInsert(nullptr),
|
|
|
|
|
m_stmtObjDelete(nullptr),
|
|
|
|
|
m_stmtObjLoad(nullptr),
|
|
|
|
|
m_stmtObjLoadAll(nullptr),
|
|
|
|
|
m_stmtUpdateLocation(nullptr),
|
|
|
|
|
m_stmtUpdateContents(nullptr),
|
|
|
|
|
m_stmtUpdateExits(nullptr),
|
|
|
|
|
m_stmtUpdateNext(nullptr),
|
|
|
|
|
m_stmtUpdateLink(nullptr),
|
|
|
|
|
m_stmtUpdateOwner(nullptr),
|
|
|
|
|
m_stmtUpdateParent(nullptr),
|
|
|
|
|
m_stmtUpdateZone(nullptr),
|
|
|
|
|
m_stmtUpdatePennies(nullptr),
|
|
|
|
|
m_stmtUpdateFlags(nullptr),
|
|
|
|
|
m_stmtUpdatePowers(nullptr),
|
|
|
|
|
m_stmtAttrGet(nullptr),
|
|
|
|
|
m_stmtAttrPut(nullptr),
|
2026-03-20 21:02:46 -06:00
|
|
|
m_stmtAttrGetModCount(nullptr),
|
2026-03-04 21:23:24 -07:00
|
|
|
m_stmtAttrDel(nullptr),
|
|
|
|
|
m_stmtAttrDelObj(nullptr),
|
|
|
|
|
m_stmtAttrGetObj(nullptr),
|
2026-03-05 08:34:03 -07:00
|
|
|
m_stmtAttrGetBuiltin(nullptr),
|
2026-03-24 05:09:02 -06:00
|
|
|
m_stmtAttrCount(nullptr),
|
2026-03-04 21:54:56 -07:00
|
|
|
m_stmtAttrNamePut(nullptr),
|
|
|
|
|
m_stmtAttrNameDel(nullptr),
|
|
|
|
|
m_stmtAttrNameLoadAll(nullptr),
|
|
|
|
|
m_stmtMetaPut(nullptr),
|
|
|
|
|
m_stmtMetaGet(nullptr),
|
2026-03-04 23:42:39 -07:00
|
|
|
m_stmtChannelSync(nullptr),
|
|
|
|
|
m_stmtChannelUserSync(nullptr),
|
|
|
|
|
m_stmtPlayerChannelSync(nullptr),
|
|
|
|
|
m_stmtChannelLoadAll(nullptr),
|
|
|
|
|
m_stmtChannelUserLoadAll(nullptr),
|
|
|
|
|
m_stmtPlayerChannelLoadAll(nullptr),
|
2026-03-05 00:18:27 -07:00
|
|
|
m_stmtChannelDelete(nullptr),
|
|
|
|
|
m_stmtChannelUserDelete(nullptr),
|
|
|
|
|
m_stmtPlayerChannelDelete(nullptr),
|
|
|
|
|
m_stmtPlayerChannelDeleteAll(nullptr),
|
2026-03-04 23:42:39 -07:00
|
|
|
m_stmtMailHeaderSync(nullptr),
|
2026-03-05 00:18:27 -07:00
|
|
|
m_stmtMailHeaderInsertRet(nullptr),
|
|
|
|
|
m_stmtMailHeaderUpdateFlags(nullptr),
|
|
|
|
|
m_stmtMailHeaderDelete(nullptr),
|
|
|
|
|
m_stmtMailHeaderDeleteAll(nullptr),
|
2026-03-04 23:42:39 -07:00
|
|
|
m_stmtMailBodySync(nullptr),
|
2026-03-05 00:18:27 -07:00
|
|
|
m_stmtMailBodyDelete(nullptr),
|
2026-03-04 23:42:39 -07:00
|
|
|
m_stmtMailAliasSync(nullptr),
|
|
|
|
|
m_stmtMailHeaderLoadAll(nullptr),
|
|
|
|
|
m_stmtMailBodyLoadAll(nullptr),
|
|
|
|
|
m_stmtMailAliasLoadAll(nullptr),
|
2026-03-10 00:00:30 -06:00
|
|
|
m_stmtConnlogInsert(nullptr),
|
|
|
|
|
m_stmtConnlogUpdate(nullptr),
|
|
|
|
|
m_stmtConnlogByPlayer(nullptr),
|
|
|
|
|
m_stmtConnlogByAddr(nullptr),
|
2026-03-11 22:12:11 -06:00
|
|
|
m_stmtCodeCacheGet(nullptr),
|
|
|
|
|
m_stmtCodeCachePut(nullptr),
|
|
|
|
|
m_stmtCodeCacheFlush(nullptr),
|
2026-03-04 21:23:24 -07:00
|
|
|
m_stats{}
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CSQLiteDB::~CSQLiteDB()
|
|
|
|
|
{
|
|
|
|
|
Close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Database lifecycle
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::Open(const char *path)
|
|
|
|
|
{
|
|
|
|
|
if (m_db)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_open(path, &m_db);
|
|
|
|
|
if (SQLITE_OK != rc)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::Open: %s\n", sqlite3_errmsg(m_db));
|
|
|
|
|
sqlite3_close(m_db);
|
|
|
|
|
m_db = nullptr;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ConfigurePragmas())
|
|
|
|
|
{
|
|
|
|
|
Close();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!CreateSchema())
|
|
|
|
|
{
|
|
|
|
|
Close();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 07:11:01 -07:00
|
|
|
if (!MigrateSchema())
|
|
|
|
|
{
|
|
|
|
|
Close();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 21:23:24 -07:00
|
|
|
if (!PrepareStatements())
|
|
|
|
|
{
|
|
|
|
|
Close();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-07 13:23:12 -05:00
|
|
|
// Remember the path we opened so callers can report exactly which file
|
|
|
|
|
// backs the database.
|
|
|
|
|
//
|
|
|
|
|
if (nullptr != path)
|
|
|
|
|
{
|
|
|
|
|
m_path.assign(path);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 21:23:24 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CSQLiteDB::Close()
|
|
|
|
|
{
|
|
|
|
|
if (m_db)
|
|
|
|
|
{
|
|
|
|
|
FinalizeStatements();
|
|
|
|
|
sqlite3_close(m_db);
|
|
|
|
|
m_db = nullptr;
|
|
|
|
|
}
|
2026-06-07 13:23:12 -05:00
|
|
|
m_path.clear();
|
2026-03-04 21:23:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::ConfigurePragmas()
|
|
|
|
|
{
|
|
|
|
|
const char *pragmas[] =
|
|
|
|
|
{
|
|
|
|
|
"PRAGMA journal_mode=WAL",
|
|
|
|
|
"PRAGMA synchronous=NORMAL",
|
2026-03-04 21:42:12 -07:00
|
|
|
"PRAGMA busy_timeout=5000",
|
2026-03-04 21:23:24 -07:00
|
|
|
"PRAGMA mmap_size=268435456",
|
|
|
|
|
"PRAGMA page_size=4096",
|
|
|
|
|
"PRAGMA cache_size=-65536",
|
2026-03-05 07:11:01 -07:00
|
|
|
"PRAGMA foreign_keys=ON",
|
2026-03-26 20:49:29 -06:00
|
|
|
|
|
|
|
|
// Disable automatic WAL checkpointing. The WAL accumulates
|
|
|
|
|
// writes without fsync during normal operation; explicit
|
|
|
|
|
// checkpoint at @dump and shutdown handles persistence.
|
|
|
|
|
// This eliminates the fsync bottleneck from code cache writes
|
|
|
|
|
// while maintaining database structural integrity.
|
|
|
|
|
"PRAGMA wal_autocheckpoint=0",
|
2026-03-04 21:23:24 -07:00
|
|
|
nullptr
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (int i = 0; pragmas[i]; i++)
|
|
|
|
|
{
|
|
|
|
|
char *errmsg = nullptr;
|
|
|
|
|
int rc = sqlite3_exec(m_db, pragmas[i], nullptr, nullptr, &errmsg);
|
|
|
|
|
if (SQLITE_OK != rc)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::ConfigurePragmas: %s: %s\n",
|
|
|
|
|
pragmas[i], errmsg ? errmsg : "unknown error");
|
|
|
|
|
sqlite3_free(errmsg);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::CreateSchema()
|
|
|
|
|
{
|
|
|
|
|
const char *schema =
|
2026-03-05 07:11:01 -07:00
|
|
|
// Core tables (no foreign key dependencies).
|
|
|
|
|
//
|
2026-03-04 21:23:24 -07:00
|
|
|
"CREATE TABLE IF NOT EXISTS objects ("
|
|
|
|
|
" dbref INTEGER PRIMARY KEY,"
|
|
|
|
|
" location INTEGER NOT NULL DEFAULT -1,"
|
|
|
|
|
" contents INTEGER NOT NULL DEFAULT -1,"
|
|
|
|
|
" exits INTEGER NOT NULL DEFAULT -1,"
|
|
|
|
|
" next INTEGER NOT NULL DEFAULT -1,"
|
|
|
|
|
" link INTEGER NOT NULL DEFAULT -1,"
|
|
|
|
|
" owner INTEGER NOT NULL DEFAULT -1,"
|
|
|
|
|
" parent INTEGER NOT NULL DEFAULT -1,"
|
|
|
|
|
" zone INTEGER NOT NULL DEFAULT -1,"
|
|
|
|
|
" pennies INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" flags1 INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" flags2 INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" flags3 INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" powers1 INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" powers2 INTEGER NOT NULL DEFAULT 0"
|
|
|
|
|
");"
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS attributes ("
|
|
|
|
|
" object INTEGER NOT NULL,"
|
|
|
|
|
" attrnum INTEGER NOT NULL,"
|
|
|
|
|
" value BLOB NOT NULL,"
|
2026-03-05 08:09:38 -07:00
|
|
|
" owner INTEGER NOT NULL DEFAULT -1,"
|
|
|
|
|
" flags INTEGER NOT NULL DEFAULT 0,"
|
2026-03-20 21:02:46 -06:00
|
|
|
" mod_count INTEGER NOT NULL DEFAULT 0,"
|
2026-03-04 21:23:24 -07:00
|
|
|
" PRIMARY KEY (object, attrnum)"
|
2026-03-04 21:54:56 -07:00
|
|
|
") WITHOUT ROWID;"
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS attrnames ("
|
|
|
|
|
" attrnum INTEGER PRIMARY KEY,"
|
|
|
|
|
" name TEXT NOT NULL,"
|
|
|
|
|
" flags INTEGER NOT NULL DEFAULT 0"
|
|
|
|
|
");"
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS metadata ("
|
|
|
|
|
" key TEXT PRIMARY KEY,"
|
|
|
|
|
" value INTEGER NOT NULL"
|
2026-03-04 23:42:39 -07:00
|
|
|
") WITHOUT ROWID;"
|
|
|
|
|
|
2026-03-05 07:11:01 -07:00
|
|
|
// Comsys: channels first (referenced), then dependents with FKs.
|
|
|
|
|
//
|
2026-03-04 23:42:39 -07:00
|
|
|
"CREATE TABLE IF NOT EXISTS channels ("
|
|
|
|
|
" name TEXT PRIMARY KEY,"
|
|
|
|
|
" header TEXT NOT NULL DEFAULT '',"
|
|
|
|
|
" type INTEGER NOT NULL DEFAULT 127,"
|
|
|
|
|
" temp1 INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" temp2 INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" charge INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" charge_who INTEGER NOT NULL DEFAULT -1,"
|
|
|
|
|
" amount_col INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" num_messages INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" chan_obj INTEGER NOT NULL DEFAULT -1"
|
|
|
|
|
") WITHOUT ROWID;"
|
|
|
|
|
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS channel_users ("
|
2026-03-05 07:11:01 -07:00
|
|
|
" channel_name TEXT NOT NULL"
|
|
|
|
|
" REFERENCES channels(name) ON DELETE CASCADE ON UPDATE CASCADE,"
|
2026-03-04 23:42:39 -07:00
|
|
|
" who INTEGER NOT NULL,"
|
|
|
|
|
" is_on INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" comtitle_status INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" gag_join_leave INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" title TEXT NOT NULL DEFAULT '',"
|
|
|
|
|
" PRIMARY KEY (channel_name, who)"
|
|
|
|
|
") WITHOUT ROWID;"
|
|
|
|
|
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS player_channels ("
|
|
|
|
|
" who INTEGER NOT NULL,"
|
|
|
|
|
" alias TEXT NOT NULL,"
|
2026-03-05 07:11:01 -07:00
|
|
|
" channel_name TEXT NOT NULL"
|
|
|
|
|
" REFERENCES channels(name) ON DELETE CASCADE ON UPDATE CASCADE,"
|
2026-03-04 23:42:39 -07:00
|
|
|
" PRIMARY KEY (who, alias)"
|
|
|
|
|
") WITHOUT ROWID;"
|
|
|
|
|
|
2026-03-05 07:11:01 -07:00
|
|
|
// Mail: bodies first (referenced), then headers with FK.
|
|
|
|
|
//
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS mail_bodies ("
|
|
|
|
|
" number INTEGER PRIMARY KEY,"
|
|
|
|
|
" message TEXT NOT NULL DEFAULT ''"
|
|
|
|
|
");"
|
|
|
|
|
|
2026-03-04 23:42:39 -07:00
|
|
|
"CREATE TABLE IF NOT EXISTS mail_headers ("
|
|
|
|
|
" rowid INTEGER PRIMARY KEY AUTOINCREMENT,"
|
|
|
|
|
" to_player INTEGER NOT NULL,"
|
|
|
|
|
" from_player INTEGER NOT NULL,"
|
2026-03-05 07:11:01 -07:00
|
|
|
" body_number INTEGER NOT NULL REFERENCES mail_bodies(number),"
|
2026-03-04 23:42:39 -07:00
|
|
|
" tolist TEXT NOT NULL DEFAULT '',"
|
|
|
|
|
" time_str TEXT NOT NULL DEFAULT '',"
|
|
|
|
|
" subject TEXT NOT NULL DEFAULT '',"
|
|
|
|
|
" read_flags INTEGER NOT NULL DEFAULT 0"
|
|
|
|
|
");"
|
|
|
|
|
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS mail_aliases ("
|
|
|
|
|
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
|
|
|
|
" owner INTEGER NOT NULL,"
|
|
|
|
|
" name TEXT NOT NULL,"
|
|
|
|
|
" description TEXT NOT NULL DEFAULT '',"
|
|
|
|
|
" desc_width INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" members TEXT NOT NULL DEFAULT ''"
|
2026-03-05 07:11:01 -07:00
|
|
|
");"
|
|
|
|
|
|
2026-03-10 00:00:30 -06:00
|
|
|
// Connection log (audit trail).
|
|
|
|
|
//
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS connlog ("
|
|
|
|
|
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
|
|
|
|
" player INTEGER NOT NULL,"
|
|
|
|
|
" connect_time INTEGER NOT NULL,"
|
|
|
|
|
" disconnect_time INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" host TEXT NOT NULL DEFAULT '',"
|
|
|
|
|
" ipaddr TEXT NOT NULL DEFAULT '',"
|
|
|
|
|
" reason TEXT NOT NULL DEFAULT ''"
|
|
|
|
|
");"
|
|
|
|
|
|
2026-03-05 07:11:01 -07:00
|
|
|
// Secondary indexes for common query patterns.
|
|
|
|
|
//
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_objects_owner ON objects(owner);"
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_objects_location ON objects(location);"
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_objects_zone ON objects(zone);"
|
2026-03-05 08:58:32 -07:00
|
|
|
"CREATE INDEX IF NOT EXISTS idx_objects_parent ON objects(parent);"
|
2026-03-05 07:11:01 -07:00
|
|
|
"CREATE INDEX IF NOT EXISTS idx_mail_headers_to ON mail_headers(to_player);"
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_channel_users_who ON channel_users(who);"
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_player_channels_channel"
|
2026-03-10 00:00:30 -06:00
|
|
|
" ON player_channels(channel_name);"
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_connlog_player ON connlog(player);"
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_connlog_time ON connlog(connect_time);"
|
2026-03-11 22:12:11 -06:00
|
|
|
"CREATE INDEX IF NOT EXISTS idx_connlog_ipaddr ON connlog(ipaddr);"
|
|
|
|
|
|
|
|
|
|
// Code cache (compiled softcode persistence).
|
|
|
|
|
//
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS code_cache ("
|
|
|
|
|
" source_hash TEXT PRIMARY KEY,"
|
|
|
|
|
" blob_hash TEXT NOT NULL,"
|
|
|
|
|
" memory_blob BLOB NOT NULL,"
|
|
|
|
|
" out_addr INTEGER NOT NULL,"
|
|
|
|
|
" needs_jit INTEGER NOT NULL,"
|
|
|
|
|
" folds INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" ecalls INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" tier2_calls INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" native_ops INTEGER NOT NULL DEFAULT 0,"
|
2026-03-20 22:52:08 -06:00
|
|
|
" compile_time INTEGER NOT NULL DEFAULT 0"
|
2026-03-11 22:12:11 -06:00
|
|
|
");";
|
2026-03-04 21:23:24 -07:00
|
|
|
|
|
|
|
|
char *errmsg = nullptr;
|
|
|
|
|
int rc = sqlite3_exec(m_db, schema, nullptr, nullptr, &errmsg);
|
|
|
|
|
if (SQLITE_OK != rc)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::CreateSchema: %s\n",
|
|
|
|
|
errmsg ? errmsg : "unknown error");
|
|
|
|
|
sqlite3_free(errmsg);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
2026-03-05 07:11:01 -07:00
|
|
|
// Schema migration
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
2026-03-05 08:09:38 -07:00
|
|
|
// Helper: run a migration SQL block with FK checks disabled.
|
|
|
|
|
//
|
|
|
|
|
static bool RunMigration(sqlite3 *db, const char *sql, int target_version)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_exec(db, "PRAGMA foreign_keys=OFF", nullptr, nullptr, nullptr);
|
|
|
|
|
|
|
|
|
|
char *errmsg = nullptr;
|
|
|
|
|
int rc = sqlite3_exec(db, sql, nullptr, nullptr, &errmsg);
|
|
|
|
|
if (SQLITE_OK != rc)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::MigrateSchema v%d: %s\n",
|
|
|
|
|
target_version, errmsg ? errmsg : "unknown error");
|
|
|
|
|
sqlite3_free(errmsg);
|
2026-06-05 17:03:35 -06:00
|
|
|
|
|
|
|
|
char *rb_errmsg = nullptr;
|
|
|
|
|
int rb_rc = sqlite3_exec(db, "ROLLBACK", nullptr, nullptr, &rb_errmsg);
|
|
|
|
|
if (SQLITE_OK != rb_rc)
|
|
|
|
|
{
|
|
|
|
|
// ROLLBACK itself failed (busy, I/O error, ...). The connection is
|
|
|
|
|
// left in an indeterminate transaction state; surface it loudly
|
|
|
|
|
// rather than dropping it on the floor. The migration aborts either
|
|
|
|
|
// way via the false return below.
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::MigrateSchema v%d: ROLLBACK failed (%d): %s\n",
|
|
|
|
|
target_version, rb_rc, rb_errmsg ? rb_errmsg : "unknown error");
|
|
|
|
|
}
|
|
|
|
|
sqlite3_free(rb_errmsg);
|
|
|
|
|
|
2026-03-05 08:09:38 -07:00
|
|
|
sqlite3_exec(db, "PRAGMA foreign_keys=ON", nullptr, nullptr, nullptr);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sqlite3_exec(db, "PRAGMA foreign_keys=ON", nullptr, nullptr, nullptr);
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::MigrateSchema: upgraded to schema version %d.\n",
|
|
|
|
|
target_version);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 07:11:01 -07:00
|
|
|
bool CSQLiteDB::MigrateSchema()
|
|
|
|
|
{
|
2026-07-24 11:42:41 -06:00
|
|
|
static const int CURRENT_SCHEMA_VERSION = 13;
|
2026-03-05 07:11:01 -07:00
|
|
|
|
|
|
|
|
int version = 0;
|
|
|
|
|
sqlite3_stmt *stmt = nullptr;
|
|
|
|
|
if (SQLITE_OK == sqlite3_prepare_v2(m_db,
|
|
|
|
|
"SELECT value FROM metadata WHERE key='schema_version'",
|
|
|
|
|
-1, &stmt, nullptr))
|
|
|
|
|
{
|
|
|
|
|
if (SQLITE_ROW == sqlite3_step(stmt))
|
|
|
|
|
{
|
|
|
|
|
version = sqlite3_column_int(stmt, 0);
|
|
|
|
|
}
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (version >= CURRENT_SCHEMA_VERSION)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 08:09:38 -07:00
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// v0/v1 -> v2: add foreign keys and secondary indexes.
|
|
|
|
|
// ---------------------------------------------------------------
|
2026-03-05 07:11:01 -07:00
|
|
|
//
|
2026-03-05 08:09:38 -07:00
|
|
|
if (version < 2)
|
2026-03-05 07:11:01 -07:00
|
|
|
{
|
2026-03-05 08:09:38 -07:00
|
|
|
const char *migration_v2 =
|
|
|
|
|
"BEGIN;"
|
|
|
|
|
|
|
|
|
|
"CREATE TABLE new_channel_users ("
|
|
|
|
|
" channel_name TEXT NOT NULL"
|
|
|
|
|
" REFERENCES channels(name) ON DELETE CASCADE ON UPDATE CASCADE,"
|
|
|
|
|
" who INTEGER NOT NULL,"
|
|
|
|
|
" is_on INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" comtitle_status INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" gag_join_leave INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" title TEXT NOT NULL DEFAULT '',"
|
|
|
|
|
" PRIMARY KEY (channel_name, who)"
|
|
|
|
|
") WITHOUT ROWID;"
|
|
|
|
|
"INSERT INTO new_channel_users"
|
|
|
|
|
" SELECT * FROM channel_users;"
|
|
|
|
|
"DROP TABLE channel_users;"
|
|
|
|
|
"ALTER TABLE new_channel_users RENAME TO channel_users;"
|
|
|
|
|
|
|
|
|
|
"CREATE TABLE new_player_channels ("
|
|
|
|
|
" who INTEGER NOT NULL,"
|
|
|
|
|
" alias TEXT NOT NULL,"
|
|
|
|
|
" channel_name TEXT NOT NULL"
|
|
|
|
|
" REFERENCES channels(name) ON DELETE CASCADE ON UPDATE CASCADE,"
|
|
|
|
|
" PRIMARY KEY (who, alias)"
|
|
|
|
|
") WITHOUT ROWID;"
|
|
|
|
|
"INSERT INTO new_player_channels"
|
|
|
|
|
" SELECT * FROM player_channels;"
|
|
|
|
|
"DROP TABLE player_channels;"
|
|
|
|
|
"ALTER TABLE new_player_channels RENAME TO player_channels;"
|
|
|
|
|
|
|
|
|
|
"CREATE TABLE new_mail_headers ("
|
|
|
|
|
" rowid INTEGER PRIMARY KEY AUTOINCREMENT,"
|
|
|
|
|
" to_player INTEGER NOT NULL,"
|
|
|
|
|
" from_player INTEGER NOT NULL,"
|
|
|
|
|
" body_number INTEGER NOT NULL REFERENCES mail_bodies(number),"
|
|
|
|
|
" tolist TEXT NOT NULL DEFAULT '',"
|
|
|
|
|
" time_str TEXT NOT NULL DEFAULT '',"
|
|
|
|
|
" subject TEXT NOT NULL DEFAULT '',"
|
|
|
|
|
" read_flags INTEGER NOT NULL DEFAULT 0"
|
|
|
|
|
");"
|
|
|
|
|
"INSERT INTO new_mail_headers"
|
|
|
|
|
" (rowid, to_player, from_player, body_number,"
|
|
|
|
|
" tolist, time_str, subject, read_flags)"
|
|
|
|
|
" SELECT rowid, to_player, from_player, body_number,"
|
|
|
|
|
" tolist, time_str, subject, read_flags"
|
|
|
|
|
" FROM mail_headers;"
|
|
|
|
|
"DROP TABLE mail_headers;"
|
|
|
|
|
"ALTER TABLE new_mail_headers RENAME TO mail_headers;"
|
|
|
|
|
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_objects_owner ON objects(owner);"
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_objects_location ON objects(location);"
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_objects_zone ON objects(zone);"
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_mail_headers_to ON mail_headers(to_player);"
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_channel_users_who ON channel_users(who);"
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_player_channels_channel"
|
|
|
|
|
" ON player_channels(channel_name);"
|
|
|
|
|
|
|
|
|
|
"INSERT OR REPLACE INTO metadata(key, value)"
|
|
|
|
|
" VALUES('schema_version', 2);"
|
|
|
|
|
|
|
|
|
|
"COMMIT;";
|
|
|
|
|
|
|
|
|
|
if (!RunMigration(m_db, migration_v2, 2))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
version = 2;
|
2026-03-05 07:11:01 -07:00
|
|
|
}
|
|
|
|
|
|
2026-03-05 08:09:38 -07:00
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// v2 -> v3: add owner/flags columns to attributes table.
|
|
|
|
|
// Recreate the table with new columns. Existing blobs may contain
|
|
|
|
|
// packed \x01owner:flags:text prefixes which we leave as-is here;
|
|
|
|
|
// the runtime atr_add_raw_LEN() decodes and re-stores them on
|
|
|
|
|
// first write. For the cold-start path, all attributes get
|
|
|
|
|
// decoded and re-stored with proper columns automatically.
|
|
|
|
|
// ---------------------------------------------------------------
|
2026-03-05 07:11:01 -07:00
|
|
|
//
|
2026-03-05 08:09:38 -07:00
|
|
|
if (version < 3)
|
|
|
|
|
{
|
|
|
|
|
const char *migration_v3 =
|
|
|
|
|
"BEGIN;"
|
|
|
|
|
|
|
|
|
|
"CREATE TABLE new_attributes ("
|
|
|
|
|
" object INTEGER NOT NULL,"
|
|
|
|
|
" attrnum INTEGER NOT NULL,"
|
|
|
|
|
" value BLOB NOT NULL,"
|
|
|
|
|
" owner INTEGER NOT NULL DEFAULT -1,"
|
|
|
|
|
" flags INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" PRIMARY KEY (object, attrnum)"
|
|
|
|
|
") WITHOUT ROWID;"
|
|
|
|
|
"INSERT INTO new_attributes (object, attrnum, value)"
|
|
|
|
|
" SELECT object, attrnum, value FROM attributes;"
|
|
|
|
|
"DROP TABLE attributes;"
|
|
|
|
|
"ALTER TABLE new_attributes RENAME TO attributes;"
|
|
|
|
|
|
|
|
|
|
"INSERT OR REPLACE INTO metadata(key, value)"
|
|
|
|
|
" VALUES('schema_version', 3);"
|
|
|
|
|
|
|
|
|
|
"COMMIT;";
|
|
|
|
|
|
|
|
|
|
if (!RunMigration(m_db, migration_v3, 3))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
version = 3;
|
|
|
|
|
}
|
2026-03-05 07:11:01 -07:00
|
|
|
|
2026-03-05 08:58:32 -07:00
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// v3 -> v4: add parent index for @search optimization.
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
//
|
|
|
|
|
if (version < 4)
|
|
|
|
|
{
|
|
|
|
|
const char *migration_v4 =
|
|
|
|
|
"BEGIN;"
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_objects_parent ON objects(parent);"
|
|
|
|
|
"INSERT OR REPLACE INTO metadata(key, value)"
|
|
|
|
|
" VALUES('schema_version', 4);"
|
|
|
|
|
"COMMIT;";
|
|
|
|
|
|
|
|
|
|
if (!RunMigration(m_db, migration_v4, 4))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
version = 4;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 14:39:01 -07:00
|
|
|
// ---------------------------------------------------------------
|
2026-03-05 14:44:40 -07:00
|
|
|
// v4 -> v5: remove legacy packed attr-253 rows.
|
2026-03-05 14:39:01 -07:00
|
|
|
// Attribute membership is derived relationally by (object, attrnum),
|
2026-03-05 14:44:40 -07:00
|
|
|
// so attr 253 is redundant and should not be persisted.
|
2026-03-05 14:39:01 -07:00
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
//
|
|
|
|
|
if (version < 5)
|
|
|
|
|
{
|
|
|
|
|
const char *migration_v5 =
|
|
|
|
|
"BEGIN;"
|
|
|
|
|
"DELETE FROM attributes WHERE attrnum=253;"
|
|
|
|
|
"INSERT OR REPLACE INTO metadata(key, value)"
|
|
|
|
|
" VALUES('schema_version', 5);"
|
|
|
|
|
"COMMIT;";
|
|
|
|
|
|
|
|
|
|
if (!RunMigration(m_db, migration_v5, 5))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
version = 5;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 00:00:30 -06:00
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// v5 -> v6: add connlog table for connection audit trail.
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
//
|
|
|
|
|
if (version < 6)
|
|
|
|
|
{
|
|
|
|
|
const char *migration_v6 =
|
|
|
|
|
"BEGIN;"
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS connlog ("
|
|
|
|
|
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
|
|
|
|
" player INTEGER NOT NULL,"
|
|
|
|
|
" connect_time INTEGER NOT NULL,"
|
|
|
|
|
" disconnect_time INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" host TEXT NOT NULL DEFAULT '',"
|
|
|
|
|
" ipaddr TEXT NOT NULL DEFAULT '',"
|
|
|
|
|
" reason TEXT NOT NULL DEFAULT ''"
|
|
|
|
|
");"
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_connlog_player ON connlog(player);"
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_connlog_time ON connlog(connect_time);"
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_connlog_ipaddr ON connlog(ipaddr);"
|
|
|
|
|
"INSERT OR REPLACE INTO metadata(key, value)"
|
|
|
|
|
" VALUES('schema_version', 6);"
|
|
|
|
|
"COMMIT;";
|
|
|
|
|
|
|
|
|
|
if (!RunMigration(m_db, migration_v6, 6))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
version = 6;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-11 22:12:11 -06:00
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// v6 -> v7: add code_cache table for compiled softcode persistence.
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
//
|
|
|
|
|
if (version < 7)
|
|
|
|
|
{
|
|
|
|
|
const char *migration_v7 =
|
|
|
|
|
"BEGIN;"
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS code_cache ("
|
|
|
|
|
" source_hash TEXT PRIMARY KEY,"
|
|
|
|
|
" blob_hash TEXT NOT NULL,"
|
|
|
|
|
" memory_blob BLOB NOT NULL,"
|
|
|
|
|
" out_addr INTEGER NOT NULL,"
|
|
|
|
|
" needs_jit INTEGER NOT NULL,"
|
|
|
|
|
" folds INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" ecalls INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" tier2_calls INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" native_ops INTEGER NOT NULL DEFAULT 0,"
|
2026-03-20 22:52:08 -06:00
|
|
|
" compile_time INTEGER NOT NULL DEFAULT 0"
|
2026-03-11 22:12:11 -06:00
|
|
|
");"
|
|
|
|
|
"INSERT OR REPLACE INTO metadata(key, value)"
|
|
|
|
|
" VALUES('schema_version', 7);"
|
|
|
|
|
"COMMIT;";
|
|
|
|
|
|
|
|
|
|
if (!RunMigration(m_db, migration_v7, 7))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
version = 7;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 21:02:46 -06:00
|
|
|
if (version < 8)
|
|
|
|
|
{
|
|
|
|
|
const char *migration_v8 =
|
|
|
|
|
"BEGIN;"
|
|
|
|
|
"ALTER TABLE attributes ADD COLUMN mod_count INTEGER NOT NULL DEFAULT 0;"
|
|
|
|
|
"INSERT OR REPLACE INTO metadata(key, value)"
|
|
|
|
|
" VALUES('schema_version', 8);"
|
|
|
|
|
"COMMIT;";
|
|
|
|
|
|
|
|
|
|
if (!RunMigration(m_db, migration_v8, 8))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
version = 8;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 22:07:39 -06:00
|
|
|
if (version < 9)
|
|
|
|
|
{
|
|
|
|
|
const char *migration_v9 =
|
|
|
|
|
"BEGIN;"
|
|
|
|
|
"ALTER TABLE code_cache ADD COLUMN deps_blob BLOB NOT NULL DEFAULT x'';"
|
|
|
|
|
"INSERT OR REPLACE INTO metadata(key, value)"
|
|
|
|
|
" VALUES('schema_version', 9);"
|
|
|
|
|
"COMMIT;";
|
|
|
|
|
|
|
|
|
|
if (!RunMigration(m_db, migration_v9, 9))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
version = 9;
|
|
|
|
|
}
|
|
|
|
|
|
Implement Phase 1 routing: static unconditional next-hop tables
Add route() softcode function with BFS-based shortest-path routing
over rooms marked NAVIGABLE. The routing table stores only the next-hop
exit for each (source, dest) pair, compressed via diagonal elimination,
adjacent marking, and row redundancy. Lazy rebuild on generation-counter
mismatch triggered by topology changes (@dig, @destroy, @link, @open,
@unlink) and NAVIGABLE flag changes. SQLite schema v10 adds route_nodes,
route_table, route_meta tables for future persistence phases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 03:18:37 -06:00
|
|
|
if (version < 10)
|
|
|
|
|
{
|
|
|
|
|
const char *migration_v10 =
|
|
|
|
|
"BEGIN;"
|
|
|
|
|
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS route_nodes ("
|
|
|
|
|
" room_dbref INTEGER PRIMARY KEY,"
|
|
|
|
|
" zone_id INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" is_navigable INTEGER NOT NULL DEFAULT 1"
|
|
|
|
|
");"
|
|
|
|
|
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS route_table ("
|
|
|
|
|
" zone_id INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" source INTEGER NOT NULL,"
|
|
|
|
|
" destination INTEGER NOT NULL,"
|
|
|
|
|
" next_exit INTEGER NOT NULL,"
|
|
|
|
|
" PRIMARY KEY (zone_id, source, destination)"
|
|
|
|
|
") WITHOUT ROWID;"
|
|
|
|
|
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS route_meta ("
|
|
|
|
|
" zone_id INTEGER PRIMARY KEY,"
|
|
|
|
|
" generation INTEGER NOT NULL DEFAULT 0,"
|
|
|
|
|
" node_count INTEGER NOT NULL DEFAULT 0"
|
|
|
|
|
");"
|
|
|
|
|
|
|
|
|
|
"INSERT OR REPLACE INTO metadata(key, value)"
|
|
|
|
|
" VALUES('schema_version', 10);"
|
|
|
|
|
|
|
|
|
|
"COMMIT;";
|
|
|
|
|
|
|
|
|
|
if (!RunMigration(m_db, migration_v10, 10))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
version = 10;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 10:01:45 -06:00
|
|
|
if (version < 11)
|
|
|
|
|
{
|
|
|
|
|
const char *migration_v11 =
|
|
|
|
|
"BEGIN;"
|
|
|
|
|
"ALTER TABLE code_cache ADD COLUMN code_blob BLOB NOT NULL DEFAULT x'';"
|
|
|
|
|
"ALTER TABLE code_cache ADD COLUMN entry_pc INTEGER NOT NULL DEFAULT 0;"
|
|
|
|
|
"ALTER TABLE code_cache ADD COLUMN code_size INTEGER NOT NULL DEFAULT 0;"
|
|
|
|
|
"ALTER TABLE code_cache ADD COLUMN str_blob BLOB NOT NULL DEFAULT x'';"
|
|
|
|
|
"ALTER TABLE code_cache ADD COLUMN str_pool_end INTEGER NOT NULL DEFAULT 0;"
|
|
|
|
|
"ALTER TABLE code_cache ADD COLUMN fargs_blob BLOB NOT NULL DEFAULT x'';"
|
|
|
|
|
"ALTER TABLE code_cache ADD COLUMN fargs_pool_end INTEGER NOT NULL DEFAULT 0;"
|
|
|
|
|
"ALTER TABLE code_cache ADD COLUMN out_pool_end INTEGER NOT NULL DEFAULT 0;"
|
|
|
|
|
"INSERT OR REPLACE INTO metadata(key, value)"
|
|
|
|
|
" VALUES('schema_version', 11);"
|
|
|
|
|
"COMMIT;";
|
|
|
|
|
|
|
|
|
|
if (!RunMigration(m_db, migration_v11, 11))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
version = 11;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-21 14:55:28 -06:00
|
|
|
if (version < 12)
|
|
|
|
|
{
|
|
|
|
|
// #1002: static function-nesting depth watermark for JIT
|
|
|
|
|
// programs (checked at run entry against the live
|
|
|
|
|
// function_recursion_limit).
|
|
|
|
|
const char *migration_v12 =
|
|
|
|
|
"BEGIN;"
|
|
|
|
|
"ALTER TABLE code_cache ADD COLUMN max_func_depth INTEGER NOT NULL DEFAULT 0;"
|
|
|
|
|
"INSERT OR REPLACE INTO metadata(key, value)"
|
|
|
|
|
" VALUES('schema_version', 12);"
|
|
|
|
|
"COMMIT;";
|
|
|
|
|
|
|
|
|
|
if (!RunMigration(m_db, migration_v12, 12))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
version = 12;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-23 20:00:47 -06:00
|
|
|
if (version < 13)
|
|
|
|
|
{
|
|
|
|
|
// Static function-invocation count watermark for JIT programs
|
|
|
|
|
// (checked at run entry against the live
|
|
|
|
|
// function_invocation_limit). Survives @restart so the defense
|
|
|
|
|
// does not need to re-learn after a cold start.
|
|
|
|
|
const char *migration_v13 =
|
|
|
|
|
"BEGIN;"
|
|
|
|
|
"ALTER TABLE code_cache ADD COLUMN n_func_calls INTEGER NOT NULL DEFAULT 0;"
|
|
|
|
|
"INSERT OR REPLACE INTO metadata(key, value)"
|
|
|
|
|
" VALUES('schema_version', 13);"
|
|
|
|
|
"COMMIT;";
|
|
|
|
|
|
|
|
|
|
if (!RunMigration(m_db, migration_v13, 13))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
version = 13;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 08:09:38 -07:00
|
|
|
// Log any FK violations (informational, not fatal).
|
2026-03-05 07:11:01 -07:00
|
|
|
//
|
|
|
|
|
if (SQLITE_OK == sqlite3_prepare_v2(m_db,
|
|
|
|
|
"PRAGMA foreign_key_check", -1, &stmt, nullptr))
|
|
|
|
|
{
|
|
|
|
|
bool first = true;
|
|
|
|
|
while (SQLITE_ROW == sqlite3_step(stmt))
|
|
|
|
|
{
|
|
|
|
|
if (first)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::MigrateSchema: foreign key violations detected:\n");
|
|
|
|
|
first = false;
|
|
|
|
|
}
|
|
|
|
|
fprintf(stderr, " table=%s rowid=%lld parent=%s fkid=%d\n",
|
|
|
|
|
sqlite3_column_text(stmt, 0),
|
|
|
|
|
sqlite3_column_int64(stmt, 1),
|
|
|
|
|
sqlite3_column_text(stmt, 2),
|
|
|
|
|
sqlite3_column_int(stmt, 3));
|
|
|
|
|
}
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
2026-03-04 21:23:24 -07:00
|
|
|
// Prepared statement management
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
static bool Prepare(sqlite3 *db, const char *sql, sqlite3_stmt **ppStmt)
|
|
|
|
|
{
|
|
|
|
|
int rc = sqlite3_prepare_v2(db, sql, -1, ppStmt, nullptr);
|
|
|
|
|
if (SQLITE_OK != rc)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::Prepare: %s\n SQL: %s\n",
|
|
|
|
|
sqlite3_errmsg(db), sql);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 10:01:45 -06:00
|
|
|
static void BindBlob(sqlite3_stmt *stmt, int index, const void *blob, int len)
|
|
|
|
|
{
|
|
|
|
|
static const char kEmptyBlob = '\0';
|
|
|
|
|
const void *data = blob;
|
|
|
|
|
if (len == 0 && data == nullptr)
|
|
|
|
|
{
|
|
|
|
|
data = &kEmptyBlob;
|
|
|
|
|
}
|
|
|
|
|
sqlite3_bind_blob(stmt, index, data, len, SQLITE_TRANSIENT);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 21:23:24 -07:00
|
|
|
bool CSQLiteDB::PrepareStatements()
|
|
|
|
|
{
|
|
|
|
|
// Object metadata.
|
|
|
|
|
//
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"INSERT OR REPLACE INTO objects "
|
|
|
|
|
"(dbref, location, contents, exits, next, link, owner, parent, zone, "
|
|
|
|
|
"pennies, flags1, flags2, flags3, powers1, powers2) "
|
|
|
|
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
|
|
|
|
&m_stmtObjInsert))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"DELETE FROM objects WHERE dbref=?",
|
|
|
|
|
&m_stmtObjDelete))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"SELECT location, contents, exits, next, link, owner, parent, zone, "
|
|
|
|
|
"pennies, flags1, flags2, flags3, powers1, powers2 "
|
|
|
|
|
"FROM objects WHERE dbref=?",
|
|
|
|
|
&m_stmtObjLoad))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"SELECT dbref, location, contents, exits, next, link, owner, parent, zone, "
|
|
|
|
|
"pennies, flags1, flags2, flags3, powers1, powers2 "
|
|
|
|
|
"FROM objects ORDER BY dbref",
|
|
|
|
|
&m_stmtObjLoadAll))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Individual field updates.
|
|
|
|
|
//
|
|
|
|
|
if (!Prepare(m_db, "UPDATE objects SET location=? WHERE dbref=?", &m_stmtUpdateLocation)) return false;
|
|
|
|
|
if (!Prepare(m_db, "UPDATE objects SET contents=? WHERE dbref=?", &m_stmtUpdateContents)) return false;
|
|
|
|
|
if (!Prepare(m_db, "UPDATE objects SET exits=? WHERE dbref=?", &m_stmtUpdateExits)) return false;
|
|
|
|
|
if (!Prepare(m_db, "UPDATE objects SET next=? WHERE dbref=?", &m_stmtUpdateNext)) return false;
|
|
|
|
|
if (!Prepare(m_db, "UPDATE objects SET link=? WHERE dbref=?", &m_stmtUpdateLink)) return false;
|
|
|
|
|
if (!Prepare(m_db, "UPDATE objects SET owner=? WHERE dbref=?", &m_stmtUpdateOwner)) return false;
|
|
|
|
|
if (!Prepare(m_db, "UPDATE objects SET parent=? WHERE dbref=?", &m_stmtUpdateParent)) return false;
|
|
|
|
|
if (!Prepare(m_db, "UPDATE objects SET zone=? WHERE dbref=?", &m_stmtUpdateZone)) return false;
|
|
|
|
|
if (!Prepare(m_db, "UPDATE objects SET pennies=? WHERE dbref=?", &m_stmtUpdatePennies)) return false;
|
|
|
|
|
if (!Prepare(m_db, "UPDATE objects SET flags1=?, flags2=?, flags3=? WHERE dbref=?", &m_stmtUpdateFlags)) return false;
|
|
|
|
|
if (!Prepare(m_db, "UPDATE objects SET powers1=?, powers2=? WHERE dbref=?", &m_stmtUpdatePowers)) return false;
|
|
|
|
|
|
|
|
|
|
// Attribute operations.
|
|
|
|
|
//
|
|
|
|
|
if (!Prepare(m_db,
|
2026-03-05 08:09:38 -07:00
|
|
|
"SELECT value, owner, flags FROM attributes WHERE object=? AND attrnum=?",
|
2026-03-04 21:23:24 -07:00
|
|
|
&m_stmtAttrGet))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
2026-03-20 21:02:46 -06:00
|
|
|
"INSERT INTO attributes (object, attrnum, value, owner, flags, mod_count)"
|
2026-03-20 21:11:02 -06:00
|
|
|
" VALUES (?,?,?,?,?,?)"
|
2026-03-20 21:02:46 -06:00
|
|
|
" ON CONFLICT(object, attrnum) DO UPDATE SET"
|
|
|
|
|
" value=excluded.value,"
|
|
|
|
|
" owner=excluded.owner,"
|
|
|
|
|
" flags=excluded.flags,"
|
2026-03-20 21:11:02 -06:00
|
|
|
" mod_count=MAX(excluded.mod_count, mod_count+1)",
|
2026-03-04 21:23:24 -07:00
|
|
|
&m_stmtAttrPut))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 21:02:46 -06:00
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"SELECT mod_count FROM attributes WHERE object=? AND attrnum=?",
|
|
|
|
|
&m_stmtAttrGetModCount))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 21:23:24 -07:00
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"DELETE FROM attributes WHERE object=? AND attrnum=?",
|
|
|
|
|
&m_stmtAttrDel))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"DELETE FROM attributes WHERE object=?",
|
|
|
|
|
&m_stmtAttrDelObj))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
2026-03-05 14:38:31 -07:00
|
|
|
"SELECT attrnum, value, owner, flags FROM attributes WHERE object=? ORDER BY attrnum",
|
2026-03-04 21:23:24 -07:00
|
|
|
&m_stmtAttrGetObj))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 08:34:03 -07:00
|
|
|
if (!Prepare(m_db,
|
2026-03-05 14:38:31 -07:00
|
|
|
"SELECT attrnum, value, owner, flags FROM attributes WHERE object=? AND attrnum < 256 ORDER BY attrnum",
|
2026-03-05 08:34:03 -07:00
|
|
|
&m_stmtAttrGetBuiltin))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-24 05:09:02 -06:00
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"SELECT COUNT(*) FROM attributes WHERE object=?",
|
|
|
|
|
&m_stmtAttrCount))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 21:54:56 -07:00
|
|
|
// Attribute name registry.
|
|
|
|
|
//
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"INSERT OR REPLACE INTO attrnames (attrnum, name, flags) VALUES (?,?,?)",
|
|
|
|
|
&m_stmtAttrNamePut))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"DELETE FROM attrnames WHERE attrnum=?",
|
|
|
|
|
&m_stmtAttrNameDel))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"SELECT attrnum, name, flags FROM attrnames ORDER BY attrnum",
|
|
|
|
|
&m_stmtAttrNameLoadAll))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Metadata key-value store.
|
|
|
|
|
//
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"INSERT OR REPLACE INTO metadata (key, value) VALUES (?,?)",
|
|
|
|
|
&m_stmtMetaPut))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"SELECT value FROM metadata WHERE key=?",
|
|
|
|
|
&m_stmtMetaGet))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 23:42:39 -07:00
|
|
|
// Comsys statements.
|
|
|
|
|
//
|
2026-07-25 08:33:42 -06:00
|
|
|
// Use ON CONFLICT DO UPDATE (not INSERT OR REPLACE). REPLACE deletes
|
|
|
|
|
// the old PRIMARY KEY row first, which fires ON DELETE CASCADE and
|
|
|
|
|
// wipes channel_users / player_channels (#1191 softcode gen-sync and
|
|
|
|
|
// any mid-session SyncChannel after membership).
|
|
|
|
|
//
|
2026-03-04 23:42:39 -07:00
|
|
|
if (!Prepare(m_db,
|
2026-07-25 08:33:42 -06:00
|
|
|
"INSERT INTO channels "
|
2026-03-04 23:42:39 -07:00
|
|
|
"(name, header, type, temp1, temp2, charge, charge_who, amount_col, num_messages, chan_obj) "
|
2026-07-25 08:33:42 -06:00
|
|
|
"VALUES (?,?,?,?,?,?,?,?,?,?) "
|
|
|
|
|
"ON CONFLICT(name) DO UPDATE SET "
|
|
|
|
|
"header=excluded.header, type=excluded.type, temp1=excluded.temp1, "
|
|
|
|
|
"temp2=excluded.temp2, charge=excluded.charge, "
|
|
|
|
|
"charge_who=excluded.charge_who, amount_col=excluded.amount_col, "
|
|
|
|
|
"num_messages=excluded.num_messages, chan_obj=excluded.chan_obj",
|
2026-03-04 23:42:39 -07:00
|
|
|
&m_stmtChannelSync))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"INSERT OR REPLACE INTO channel_users "
|
|
|
|
|
"(channel_name, who, is_on, comtitle_status, gag_join_leave, title) "
|
|
|
|
|
"VALUES (?,?,?,?,?,?)",
|
|
|
|
|
&m_stmtChannelUserSync))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"INSERT OR REPLACE INTO player_channels (who, alias, channel_name) "
|
|
|
|
|
"VALUES (?,?,?)",
|
|
|
|
|
&m_stmtPlayerChannelSync))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"SELECT name, header, type, temp1, temp2, charge, charge_who, "
|
|
|
|
|
"amount_col, num_messages, chan_obj FROM channels ORDER BY name",
|
|
|
|
|
&m_stmtChannelLoadAll))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"SELECT channel_name, who, is_on, comtitle_status, gag_join_leave, title "
|
|
|
|
|
"FROM channel_users ORDER BY channel_name, who",
|
|
|
|
|
&m_stmtChannelUserLoadAll))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"SELECT who, alias, channel_name FROM player_channels ORDER BY who, alias",
|
|
|
|
|
&m_stmtPlayerChannelLoadAll))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 00:18:27 -07:00
|
|
|
// Comsys write-through delete statements.
|
|
|
|
|
//
|
|
|
|
|
if (!Prepare(m_db, "DELETE FROM channels WHERE name=?", &m_stmtChannelDelete)) return false;
|
|
|
|
|
if (!Prepare(m_db, "DELETE FROM channel_users WHERE channel_name=? AND who=?", &m_stmtChannelUserDelete)) return false;
|
|
|
|
|
if (!Prepare(m_db, "DELETE FROM player_channels WHERE who=? AND alias=?", &m_stmtPlayerChannelDelete)) return false;
|
|
|
|
|
if (!Prepare(m_db, "DELETE FROM player_channels WHERE who=?", &m_stmtPlayerChannelDeleteAll)) return false;
|
|
|
|
|
|
2026-03-04 23:42:39 -07:00
|
|
|
// Mail statements.
|
|
|
|
|
//
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"INSERT INTO mail_headers "
|
|
|
|
|
"(to_player, from_player, body_number, tolist, time_str, subject, read_flags) "
|
|
|
|
|
"VALUES (?,?,?,?,?,?,?)",
|
|
|
|
|
&m_stmtMailHeaderSync))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"INSERT OR REPLACE INTO mail_bodies (number, message) VALUES (?,?)",
|
|
|
|
|
&m_stmtMailBodySync))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 00:18:27 -07:00
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"INSERT INTO mail_headers "
|
|
|
|
|
"(to_player, from_player, body_number, tolist, time_str, subject, read_flags) "
|
|
|
|
|
"VALUES (?,?,?,?,?,?,?)",
|
|
|
|
|
&m_stmtMailHeaderInsertRet))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db, "UPDATE mail_headers SET read_flags=? WHERE rowid=?", &m_stmtMailHeaderUpdateFlags)) return false;
|
|
|
|
|
if (!Prepare(m_db, "DELETE FROM mail_headers WHERE rowid=?", &m_stmtMailHeaderDelete)) return false;
|
|
|
|
|
if (!Prepare(m_db, "DELETE FROM mail_headers WHERE to_player=?", &m_stmtMailHeaderDeleteAll)) return false;
|
|
|
|
|
if (!Prepare(m_db, "DELETE FROM mail_bodies WHERE number=?", &m_stmtMailBodyDelete)) return false;
|
|
|
|
|
|
2026-03-04 23:42:39 -07:00
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"INSERT INTO mail_aliases (owner, name, description, desc_width, members) "
|
|
|
|
|
"VALUES (?,?,?,?,?)",
|
|
|
|
|
&m_stmtMailAliasSync))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
2026-03-05 00:18:27 -07:00
|
|
|
"SELECT rowid, to_player, from_player, body_number, tolist, time_str, subject, read_flags "
|
2026-03-04 23:42:39 -07:00
|
|
|
"FROM mail_headers ORDER BY rowid",
|
|
|
|
|
&m_stmtMailHeaderLoadAll))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"SELECT number, message FROM mail_bodies ORDER BY number",
|
|
|
|
|
&m_stmtMailBodyLoadAll))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"SELECT owner, name, description, desc_width, members "
|
|
|
|
|
"FROM mail_aliases ORDER BY id",
|
|
|
|
|
&m_stmtMailAliasLoadAll))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 00:00:30 -06:00
|
|
|
// Connlog statements.
|
|
|
|
|
//
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"INSERT INTO connlog (player, connect_time, host, ipaddr)"
|
|
|
|
|
" VALUES (?, ?, ?, ?)",
|
|
|
|
|
&m_stmtConnlogInsert))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"UPDATE connlog SET disconnect_time=?, reason=? WHERE id=?",
|
|
|
|
|
&m_stmtConnlogUpdate))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"SELECT id, player, connect_time, disconnect_time, host, ipaddr, reason"
|
|
|
|
|
" FROM connlog WHERE player=? ORDER BY connect_time DESC LIMIT ?",
|
|
|
|
|
&m_stmtConnlogByPlayer))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"SELECT id, player, connect_time, disconnect_time, host, ipaddr, reason"
|
|
|
|
|
" FROM connlog WHERE ipaddr LIKE ? ORDER BY connect_time DESC LIMIT ?",
|
|
|
|
|
&m_stmtConnlogByAddr))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-11 22:12:11 -06:00
|
|
|
// Code cache statements.
|
|
|
|
|
//
|
|
|
|
|
if (!Prepare(m_db,
|
2026-04-04 10:01:45 -06:00
|
|
|
"SELECT memory_blob, code_blob, entry_pc, code_size, str_blob, str_pool_end,"
|
|
|
|
|
" fargs_blob, fargs_pool_end, out_pool_end,"
|
2026-07-21 14:55:28 -06:00
|
|
|
" out_addr, needs_jit, folds, ecalls, tier2_calls, native_ops, deps_blob,"
|
2026-07-23 20:00:47 -06:00
|
|
|
" max_func_depth, n_func_calls"
|
2026-03-11 22:12:11 -06:00
|
|
|
" FROM code_cache WHERE source_hash=? AND blob_hash=?",
|
|
|
|
|
&m_stmtCodeCacheGet))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"INSERT OR REPLACE INTO code_cache"
|
2026-04-04 10:01:45 -06:00
|
|
|
" (source_hash, blob_hash, memory_blob, code_blob, entry_pc, code_size,"
|
|
|
|
|
" str_blob, str_pool_end, fargs_blob, fargs_pool_end, out_pool_end,"
|
|
|
|
|
" out_addr, needs_jit, folds, ecalls, tier2_calls, native_ops,"
|
2026-07-23 20:00:47 -06:00
|
|
|
" compile_time, deps_blob, max_func_depth, n_func_calls)"
|
|
|
|
|
" VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
2026-03-11 22:12:11 -06:00
|
|
|
&m_stmtCodeCachePut))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Prepare(m_db,
|
|
|
|
|
"DELETE FROM code_cache",
|
|
|
|
|
&m_stmtCodeCacheFlush))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 21:23:24 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void Finalize(sqlite3_stmt **ppStmt)
|
|
|
|
|
{
|
|
|
|
|
if (*ppStmt)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_finalize(*ppStmt);
|
|
|
|
|
*ppStmt = nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CSQLiteDB::FinalizeStatements()
|
|
|
|
|
{
|
|
|
|
|
Finalize(&m_stmtObjInsert);
|
|
|
|
|
Finalize(&m_stmtObjDelete);
|
|
|
|
|
Finalize(&m_stmtObjLoad);
|
|
|
|
|
Finalize(&m_stmtObjLoadAll);
|
|
|
|
|
Finalize(&m_stmtUpdateLocation);
|
|
|
|
|
Finalize(&m_stmtUpdateContents);
|
|
|
|
|
Finalize(&m_stmtUpdateExits);
|
|
|
|
|
Finalize(&m_stmtUpdateNext);
|
|
|
|
|
Finalize(&m_stmtUpdateLink);
|
|
|
|
|
Finalize(&m_stmtUpdateOwner);
|
|
|
|
|
Finalize(&m_stmtUpdateParent);
|
|
|
|
|
Finalize(&m_stmtUpdateZone);
|
|
|
|
|
Finalize(&m_stmtUpdatePennies);
|
|
|
|
|
Finalize(&m_stmtUpdateFlags);
|
|
|
|
|
Finalize(&m_stmtUpdatePowers);
|
|
|
|
|
Finalize(&m_stmtAttrGet);
|
|
|
|
|
Finalize(&m_stmtAttrPut);
|
|
|
|
|
Finalize(&m_stmtAttrDel);
|
|
|
|
|
Finalize(&m_stmtAttrDelObj);
|
|
|
|
|
Finalize(&m_stmtAttrGetObj);
|
2026-03-05 08:34:03 -07:00
|
|
|
Finalize(&m_stmtAttrGetBuiltin);
|
2026-03-24 05:09:02 -06:00
|
|
|
Finalize(&m_stmtAttrCount);
|
2026-03-04 21:54:56 -07:00
|
|
|
Finalize(&m_stmtAttrNamePut);
|
|
|
|
|
Finalize(&m_stmtAttrNameDel);
|
|
|
|
|
Finalize(&m_stmtAttrNameLoadAll);
|
|
|
|
|
Finalize(&m_stmtMetaPut);
|
|
|
|
|
Finalize(&m_stmtMetaGet);
|
2026-03-04 23:42:39 -07:00
|
|
|
Finalize(&m_stmtChannelSync);
|
|
|
|
|
Finalize(&m_stmtChannelUserSync);
|
|
|
|
|
Finalize(&m_stmtPlayerChannelSync);
|
|
|
|
|
Finalize(&m_stmtChannelLoadAll);
|
|
|
|
|
Finalize(&m_stmtChannelUserLoadAll);
|
|
|
|
|
Finalize(&m_stmtPlayerChannelLoadAll);
|
2026-03-05 00:18:27 -07:00
|
|
|
Finalize(&m_stmtChannelDelete);
|
|
|
|
|
Finalize(&m_stmtChannelUserDelete);
|
|
|
|
|
Finalize(&m_stmtPlayerChannelDelete);
|
|
|
|
|
Finalize(&m_stmtPlayerChannelDeleteAll);
|
2026-03-04 23:42:39 -07:00
|
|
|
Finalize(&m_stmtMailHeaderSync);
|
2026-03-05 00:18:27 -07:00
|
|
|
Finalize(&m_stmtMailHeaderInsertRet);
|
|
|
|
|
Finalize(&m_stmtMailHeaderUpdateFlags);
|
|
|
|
|
Finalize(&m_stmtMailHeaderDelete);
|
|
|
|
|
Finalize(&m_stmtMailHeaderDeleteAll);
|
2026-03-04 23:42:39 -07:00
|
|
|
Finalize(&m_stmtMailBodySync);
|
2026-03-05 00:18:27 -07:00
|
|
|
Finalize(&m_stmtMailBodyDelete);
|
2026-03-04 23:42:39 -07:00
|
|
|
Finalize(&m_stmtMailAliasSync);
|
|
|
|
|
Finalize(&m_stmtMailHeaderLoadAll);
|
|
|
|
|
Finalize(&m_stmtMailBodyLoadAll);
|
|
|
|
|
Finalize(&m_stmtMailAliasLoadAll);
|
2026-03-10 00:00:30 -06:00
|
|
|
Finalize(&m_stmtConnlogInsert);
|
|
|
|
|
Finalize(&m_stmtConnlogUpdate);
|
|
|
|
|
Finalize(&m_stmtConnlogByPlayer);
|
|
|
|
|
Finalize(&m_stmtConnlogByAddr);
|
2026-03-11 22:12:11 -06:00
|
|
|
Finalize(&m_stmtCodeCacheGet);
|
|
|
|
|
Finalize(&m_stmtCodeCachePut);
|
|
|
|
|
Finalize(&m_stmtCodeCacheFlush);
|
2026-03-04 21:23:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Object metadata operations
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::InsertObject(const ObjectRecord &obj)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtObjInsert, 1, obj.dbref_val);
|
|
|
|
|
sqlite3_bind_int(m_stmtObjInsert, 2, obj.location);
|
|
|
|
|
sqlite3_bind_int(m_stmtObjInsert, 3, obj.contents);
|
|
|
|
|
sqlite3_bind_int(m_stmtObjInsert, 4, obj.exits);
|
|
|
|
|
sqlite3_bind_int(m_stmtObjInsert, 5, obj.next);
|
|
|
|
|
sqlite3_bind_int(m_stmtObjInsert, 6, obj.link);
|
|
|
|
|
sqlite3_bind_int(m_stmtObjInsert, 7, obj.owner);
|
|
|
|
|
sqlite3_bind_int(m_stmtObjInsert, 8, obj.parent);
|
|
|
|
|
sqlite3_bind_int(m_stmtObjInsert, 9, obj.zone);
|
|
|
|
|
sqlite3_bind_int(m_stmtObjInsert, 10, obj.pennies);
|
|
|
|
|
sqlite3_bind_int(m_stmtObjInsert, 11, static_cast<int>(obj.flags1));
|
|
|
|
|
sqlite3_bind_int(m_stmtObjInsert, 12, static_cast<int>(obj.flags2));
|
|
|
|
|
sqlite3_bind_int(m_stmtObjInsert, 13, static_cast<int>(obj.flags3));
|
|
|
|
|
sqlite3_bind_int(m_stmtObjInsert, 14, obj.powers1);
|
|
|
|
|
sqlite3_bind_int(m_stmtObjInsert, 15, obj.powers2);
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_step(m_stmtObjInsert);
|
|
|
|
|
sqlite3_reset(m_stmtObjInsert);
|
|
|
|
|
|
|
|
|
|
if (SQLITE_DONE == rc)
|
|
|
|
|
{
|
|
|
|
|
m_stats.obj_inserts++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::InsertObject(#%d): %s\n",
|
|
|
|
|
obj.dbref_val, sqlite3_errmsg(m_db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::DeleteObject(dbref obj)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtObjDelete, 1, obj);
|
|
|
|
|
int rc = sqlite3_step(m_stmtObjDelete);
|
|
|
|
|
sqlite3_reset(m_stmtObjDelete);
|
|
|
|
|
|
|
|
|
|
if (SQLITE_DONE == rc)
|
|
|
|
|
{
|
|
|
|
|
m_stats.obj_deletes++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 15:42:21 -07:00
|
|
|
bool CSQLiteDB::ClearObjectTable()
|
|
|
|
|
{
|
|
|
|
|
return SQLITE_OK == sqlite3_exec(m_db, "DELETE FROM objects;", nullptr, nullptr, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 21:23:24 -07:00
|
|
|
bool CSQLiteDB::LoadObject(dbref obj, ObjectRecord &rec)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtObjLoad, 1, obj);
|
|
|
|
|
int rc = sqlite3_step(m_stmtObjLoad);
|
|
|
|
|
|
|
|
|
|
if (SQLITE_ROW == rc)
|
|
|
|
|
{
|
|
|
|
|
rec.dbref_val = obj;
|
|
|
|
|
rec.location = sqlite3_column_int(m_stmtObjLoad, 0);
|
|
|
|
|
rec.contents = sqlite3_column_int(m_stmtObjLoad, 1);
|
|
|
|
|
rec.exits = sqlite3_column_int(m_stmtObjLoad, 2);
|
|
|
|
|
rec.next = sqlite3_column_int(m_stmtObjLoad, 3);
|
|
|
|
|
rec.link = sqlite3_column_int(m_stmtObjLoad, 4);
|
|
|
|
|
rec.owner = sqlite3_column_int(m_stmtObjLoad, 5);
|
|
|
|
|
rec.parent = sqlite3_column_int(m_stmtObjLoad, 6);
|
|
|
|
|
rec.zone = sqlite3_column_int(m_stmtObjLoad, 7);
|
|
|
|
|
rec.pennies = sqlite3_column_int(m_stmtObjLoad, 8);
|
|
|
|
|
rec.flags1 = static_cast<FLAG>(sqlite3_column_int(m_stmtObjLoad, 9));
|
|
|
|
|
rec.flags2 = static_cast<FLAG>(sqlite3_column_int(m_stmtObjLoad, 10));
|
|
|
|
|
rec.flags3 = static_cast<FLAG>(sqlite3_column_int(m_stmtObjLoad, 11));
|
|
|
|
|
rec.powers1 = sqlite3_column_int(m_stmtObjLoad, 12);
|
|
|
|
|
rec.powers2 = sqlite3_column_int(m_stmtObjLoad, 13);
|
|
|
|
|
sqlite3_reset(m_stmtObjLoad);
|
|
|
|
|
m_stats.obj_loads++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sqlite3_reset(m_stmtObjLoad);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::LoadAllObjects(ObjectCallback cb)
|
|
|
|
|
{
|
2026-03-05 15:11:15 -07:00
|
|
|
int rc = SQLITE_DONE;
|
2026-03-04 21:23:24 -07:00
|
|
|
for (;;)
|
|
|
|
|
{
|
2026-03-05 15:11:15 -07:00
|
|
|
rc = sqlite3_step(m_stmtObjLoadAll);
|
2026-03-04 21:23:24 -07:00
|
|
|
if (SQLITE_ROW != rc)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ObjectRecord rec;
|
|
|
|
|
rec.dbref_val = sqlite3_column_int(m_stmtObjLoadAll, 0);
|
|
|
|
|
rec.location = sqlite3_column_int(m_stmtObjLoadAll, 1);
|
|
|
|
|
rec.contents = sqlite3_column_int(m_stmtObjLoadAll, 2);
|
|
|
|
|
rec.exits = sqlite3_column_int(m_stmtObjLoadAll, 3);
|
|
|
|
|
rec.next = sqlite3_column_int(m_stmtObjLoadAll, 4);
|
|
|
|
|
rec.link = sqlite3_column_int(m_stmtObjLoadAll, 5);
|
|
|
|
|
rec.owner = sqlite3_column_int(m_stmtObjLoadAll, 6);
|
|
|
|
|
rec.parent = sqlite3_column_int(m_stmtObjLoadAll, 7);
|
|
|
|
|
rec.zone = sqlite3_column_int(m_stmtObjLoadAll, 8);
|
|
|
|
|
rec.pennies = sqlite3_column_int(m_stmtObjLoadAll, 9);
|
|
|
|
|
rec.flags1 = static_cast<FLAG>(sqlite3_column_int(m_stmtObjLoadAll, 10));
|
|
|
|
|
rec.flags2 = static_cast<FLAG>(sqlite3_column_int(m_stmtObjLoadAll, 11));
|
|
|
|
|
rec.flags3 = static_cast<FLAG>(sqlite3_column_int(m_stmtObjLoadAll, 12));
|
|
|
|
|
rec.powers1 = sqlite3_column_int(m_stmtObjLoadAll, 13);
|
|
|
|
|
rec.powers2 = sqlite3_column_int(m_stmtObjLoadAll, 14);
|
|
|
|
|
|
|
|
|
|
m_stats.obj_loads++;
|
|
|
|
|
cb(rec);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sqlite3_reset(m_stmtObjLoadAll);
|
2026-03-05 15:11:15 -07:00
|
|
|
if (SQLITE_DONE != rc)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::LoadAllObjects: %s\n", sqlite3_errmsg(m_db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-04 21:23:24 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Individual field updates
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::UpdateSingleField(sqlite3_stmt *stmt, dbref obj, int val)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(stmt, 1, val);
|
|
|
|
|
sqlite3_bind_int(stmt, 2, obj);
|
|
|
|
|
int rc = sqlite3_step(stmt);
|
|
|
|
|
sqlite3_reset(stmt);
|
|
|
|
|
|
|
|
|
|
if (SQLITE_DONE == rc)
|
|
|
|
|
{
|
|
|
|
|
m_stats.obj_updates++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::UpdateLocation(dbref obj, dbref val) { return UpdateSingleField(m_stmtUpdateLocation, obj, val); }
|
|
|
|
|
bool CSQLiteDB::UpdateContents(dbref obj, dbref val) { return UpdateSingleField(m_stmtUpdateContents, obj, val); }
|
|
|
|
|
bool CSQLiteDB::UpdateExits(dbref obj, dbref val) { return UpdateSingleField(m_stmtUpdateExits, obj, val); }
|
|
|
|
|
bool CSQLiteDB::UpdateNext(dbref obj, dbref val) { return UpdateSingleField(m_stmtUpdateNext, obj, val); }
|
|
|
|
|
bool CSQLiteDB::UpdateLink(dbref obj, dbref val) { return UpdateSingleField(m_stmtUpdateLink, obj, val); }
|
|
|
|
|
bool CSQLiteDB::UpdateOwner(dbref obj, dbref val) { return UpdateSingleField(m_stmtUpdateOwner, obj, val); }
|
|
|
|
|
bool CSQLiteDB::UpdateParent(dbref obj, dbref val) { return UpdateSingleField(m_stmtUpdateParent, obj, val); }
|
|
|
|
|
bool CSQLiteDB::UpdateZone(dbref obj, dbref val) { return UpdateSingleField(m_stmtUpdateZone, obj, val); }
|
|
|
|
|
bool CSQLiteDB::UpdatePennies(dbref obj, int val) { return UpdateSingleField(m_stmtUpdatePennies, obj, val); }
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::UpdateFlags(dbref obj, FLAG f1, FLAG f2, FLAG f3)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtUpdateFlags, 1, static_cast<int>(f1));
|
|
|
|
|
sqlite3_bind_int(m_stmtUpdateFlags, 2, static_cast<int>(f2));
|
|
|
|
|
sqlite3_bind_int(m_stmtUpdateFlags, 3, static_cast<int>(f3));
|
|
|
|
|
sqlite3_bind_int(m_stmtUpdateFlags, 4, obj);
|
|
|
|
|
int rc = sqlite3_step(m_stmtUpdateFlags);
|
|
|
|
|
sqlite3_reset(m_stmtUpdateFlags);
|
|
|
|
|
|
|
|
|
|
if (SQLITE_DONE == rc)
|
|
|
|
|
{
|
|
|
|
|
m_stats.obj_updates++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::UpdatePowers(dbref obj, POWER p1, POWER p2)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtUpdatePowers, 1, p1);
|
|
|
|
|
sqlite3_bind_int(m_stmtUpdatePowers, 2, p2);
|
|
|
|
|
sqlite3_bind_int(m_stmtUpdatePowers, 3, obj);
|
|
|
|
|
int rc = sqlite3_step(m_stmtUpdatePowers);
|
|
|
|
|
sqlite3_reset(m_stmtUpdatePowers);
|
|
|
|
|
|
|
|
|
|
if (SQLITE_DONE == rc)
|
|
|
|
|
{
|
|
|
|
|
m_stats.obj_updates++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Attribute operations
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
2026-03-05 08:09:38 -07:00
|
|
|
bool CSQLiteDB::GetAttribute(dbref obj, int attrnum, UTF8 *buf, size_t buflen,
|
|
|
|
|
size_t *pLen, dbref *owner, int *flags)
|
2026-03-04 21:23:24 -07:00
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtAttrGet, 1, obj);
|
|
|
|
|
sqlite3_bind_int(m_stmtAttrGet, 2, attrnum);
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_step(m_stmtAttrGet);
|
|
|
|
|
if (SQLITE_ROW == rc)
|
|
|
|
|
{
|
|
|
|
|
const void *blob = sqlite3_column_blob(m_stmtAttrGet, 0);
|
|
|
|
|
size_t blobLen = static_cast<size_t>(sqlite3_column_bytes(m_stmtAttrGet, 0));
|
|
|
|
|
|
2026-06-05 17:03:35 -06:00
|
|
|
// sqlite3_column_blob() can return NULL with bytes > 0 only under OOM.
|
|
|
|
|
// Treat that as "not found" rather than memcpy'ing from NULL. A genuine
|
|
|
|
|
// zero-length attribute returns NULL with blobLen == 0 and is valid.
|
|
|
|
|
if (NULL == blob && 0 != blobLen)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_reset(m_stmtAttrGet);
|
|
|
|
|
*pLen = 0;
|
|
|
|
|
if (owner) *owner = NOTHING;
|
|
|
|
|
if (flags) *flags = 0;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 21:23:24 -07:00
|
|
|
if (blobLen > buflen)
|
|
|
|
|
{
|
|
|
|
|
blobLen = buflen;
|
|
|
|
|
}
|
2026-06-05 17:03:35 -06:00
|
|
|
if (0 != blobLen)
|
|
|
|
|
{
|
|
|
|
|
memcpy(buf, blob, blobLen);
|
|
|
|
|
}
|
2026-03-04 21:23:24 -07:00
|
|
|
*pLen = blobLen;
|
2026-03-05 23:11:03 -07:00
|
|
|
if (owner) *owner = static_cast<dbref>(sqlite3_column_int(m_stmtAttrGet, 1));
|
|
|
|
|
if (flags) *flags = sqlite3_column_int(m_stmtAttrGet, 2);
|
2026-03-04 21:23:24 -07:00
|
|
|
sqlite3_reset(m_stmtAttrGet);
|
|
|
|
|
m_stats.attr_gets++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sqlite3_reset(m_stmtAttrGet);
|
|
|
|
|
*pLen = 0;
|
2026-03-05 23:11:03 -07:00
|
|
|
if (owner) *owner = NOTHING;
|
|
|
|
|
if (flags) *flags = 0;
|
2026-03-04 21:23:24 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 08:09:38 -07:00
|
|
|
bool CSQLiteDB::PutAttribute(dbref obj, int attrnum, const UTF8 *value, size_t len,
|
|
|
|
|
dbref owner, int flags)
|
2026-03-04 21:23:24 -07:00
|
|
|
{
|
2026-03-20 21:20:16 -06:00
|
|
|
// Read current in-memory mod_count and add 1 for this write.
|
|
|
|
|
// The SQL upsert uses MAX(excluded, current+1) to ensure
|
|
|
|
|
// monotonicity even if the SQLite row has a higher value.
|
|
|
|
|
// attr_mod_count_inc() in atr_add_raw_LEN runs AFTER this
|
|
|
|
|
// call succeeds, so we pre-compute the next value here.
|
|
|
|
|
uint32_t mc = attr_mod_count_get(obj, attrnum) + 1;
|
2026-03-20 21:11:02 -06:00
|
|
|
|
2026-03-04 21:23:24 -07:00
|
|
|
sqlite3_bind_int(m_stmtAttrPut, 1, obj);
|
|
|
|
|
sqlite3_bind_int(m_stmtAttrPut, 2, attrnum);
|
|
|
|
|
sqlite3_bind_blob(m_stmtAttrPut, 3, value, static_cast<int>(len), SQLITE_STATIC);
|
2026-03-05 08:09:38 -07:00
|
|
|
sqlite3_bind_int(m_stmtAttrPut, 4, owner);
|
|
|
|
|
sqlite3_bind_int(m_stmtAttrPut, 5, flags);
|
2026-03-20 21:11:02 -06:00
|
|
|
sqlite3_bind_int(m_stmtAttrPut, 6, static_cast<int>(mc));
|
2026-03-04 21:23:24 -07:00
|
|
|
|
|
|
|
|
int rc = sqlite3_step(m_stmtAttrPut);
|
|
|
|
|
sqlite3_reset(m_stmtAttrPut);
|
|
|
|
|
|
|
|
|
|
if (SQLITE_DONE == rc)
|
|
|
|
|
{
|
|
|
|
|
m_stats.attr_puts++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::PutAttribute(#%d/%d): %s\n",
|
|
|
|
|
obj, attrnum, sqlite3_errmsg(m_db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 21:02:46 -06:00
|
|
|
uint32_t CSQLiteDB::GetAttrModCount(dbref obj, int attrnum)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtAttrGetModCount, 1, obj);
|
|
|
|
|
sqlite3_bind_int(m_stmtAttrGetModCount, 2, attrnum);
|
|
|
|
|
|
|
|
|
|
uint32_t result = 0;
|
|
|
|
|
int rc = sqlite3_step(m_stmtAttrGetModCount);
|
|
|
|
|
if (SQLITE_ROW == rc)
|
|
|
|
|
{
|
|
|
|
|
result = static_cast<uint32_t>(sqlite3_column_int(m_stmtAttrGetModCount, 0));
|
|
|
|
|
}
|
|
|
|
|
sqlite3_reset(m_stmtAttrGetModCount);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 21:25:50 -06:00
|
|
|
void CSQLiteDB::GetAllAttrModCounts(dbref obj,
|
|
|
|
|
std::function<void(int attrnum, uint32_t mc)> cb)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_stmt *stmt = nullptr;
|
|
|
|
|
if (SQLITE_OK != sqlite3_prepare_v2(m_db,
|
|
|
|
|
"SELECT attrnum, mod_count FROM attributes WHERE object=?",
|
|
|
|
|
-1, &stmt, nullptr))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
sqlite3_bind_int(stmt, 1, obj);
|
|
|
|
|
while (SQLITE_ROW == sqlite3_step(stmt))
|
|
|
|
|
{
|
|
|
|
|
int attrnum = sqlite3_column_int(stmt, 0);
|
|
|
|
|
uint32_t mc = static_cast<uint32_t>(sqlite3_column_int(stmt, 1));
|
|
|
|
|
cb(attrnum, mc);
|
|
|
|
|
}
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 21:23:24 -07:00
|
|
|
bool CSQLiteDB::DelAttribute(dbref obj, int attrnum)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtAttrDel, 1, obj);
|
|
|
|
|
sqlite3_bind_int(m_stmtAttrDel, 2, attrnum);
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_step(m_stmtAttrDel);
|
|
|
|
|
sqlite3_reset(m_stmtAttrDel);
|
|
|
|
|
|
|
|
|
|
if (SQLITE_DONE == rc)
|
|
|
|
|
{
|
|
|
|
|
m_stats.attr_dels++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::DelAllAttributes(dbref obj)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtAttrDelObj, 1, obj);
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_step(m_stmtAttrDelObj);
|
|
|
|
|
sqlite3_reset(m_stmtAttrDelObj);
|
|
|
|
|
|
|
|
|
|
if (SQLITE_DONE == rc)
|
|
|
|
|
{
|
|
|
|
|
m_stats.attr_dels++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-24 05:09:02 -06:00
|
|
|
int CSQLiteDB::CountAttributes(dbref obj)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtAttrCount, 1, obj);
|
|
|
|
|
int count = 0;
|
|
|
|
|
int rc = sqlite3_step(m_stmtAttrCount);
|
|
|
|
|
if (SQLITE_ROW == rc)
|
|
|
|
|
{
|
|
|
|
|
count = sqlite3_column_int(m_stmtAttrCount, 0);
|
|
|
|
|
}
|
|
|
|
|
sqlite3_reset(m_stmtAttrCount);
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-24 05:40:48 -06:00
|
|
|
std::vector<int> CSQLiteDB::FindOrphanedAttrNames()
|
|
|
|
|
{
|
|
|
|
|
std::vector<int> orphans;
|
|
|
|
|
const char *sql =
|
|
|
|
|
"SELECT attrnum FROM attrnames WHERE attrnum >= 256"
|
|
|
|
|
" AND attrnum NOT IN (SELECT DISTINCT attrnum FROM attributes)";
|
|
|
|
|
|
|
|
|
|
sqlite3_stmt *stmt = nullptr;
|
|
|
|
|
if (SQLITE_OK != sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr))
|
|
|
|
|
{
|
|
|
|
|
return orphans;
|
|
|
|
|
}
|
|
|
|
|
while (SQLITE_ROW == sqlite3_step(stmt))
|
|
|
|
|
{
|
|
|
|
|
orphans.push_back(sqlite3_column_int(stmt, 0));
|
|
|
|
|
}
|
|
|
|
|
sqlite3_finalize(stmt);
|
|
|
|
|
return orphans;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int CSQLiteDB::PurgeOrphanedAttrNames()
|
|
|
|
|
{
|
|
|
|
|
// Delete vattr names (attrnum >= A_USER_START) that are not
|
|
|
|
|
// referenced by any row in the attributes table.
|
|
|
|
|
//
|
|
|
|
|
const char *sql =
|
|
|
|
|
"DELETE FROM attrnames WHERE attrnum >= 256"
|
|
|
|
|
" AND attrnum NOT IN (SELECT DISTINCT attrnum FROM attributes)";
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_exec(m_db, sql, nullptr, nullptr, nullptr);
|
|
|
|
|
if (SQLITE_OK != rc)
|
|
|
|
|
{
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
return sqlite3_changes(m_db);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CSQLiteDB::Analyze()
|
|
|
|
|
{
|
|
|
|
|
sqlite3_exec(m_db, "ANALYZE", nullptr, nullptr, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 15:47:04 -07:00
|
|
|
bool CSQLiteDB::ClearAttributes()
|
|
|
|
|
{
|
2026-03-20 21:20:16 -06:00
|
|
|
bool ok = SQLITE_OK == sqlite3_exec(m_db, "DELETE FROM attributes;", nullptr, nullptr, nullptr);
|
|
|
|
|
if (ok)
|
|
|
|
|
{
|
|
|
|
|
attr_mod_count_invalidate_all();
|
|
|
|
|
}
|
|
|
|
|
return ok;
|
2026-03-05 15:47:04 -07:00
|
|
|
}
|
|
|
|
|
|
2026-03-04 21:23:24 -07:00
|
|
|
bool CSQLiteDB::GetAllAttributes(dbref obj, AttrCallback cb)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtAttrGetObj, 1, obj);
|
|
|
|
|
|
2026-03-05 15:31:54 -07:00
|
|
|
int rc = SQLITE_DONE;
|
2026-03-04 21:23:24 -07:00
|
|
|
for (;;)
|
|
|
|
|
{
|
2026-03-05 15:31:54 -07:00
|
|
|
rc = sqlite3_step(m_stmtAttrGetObj);
|
2026-03-04 21:23:24 -07:00
|
|
|
if (SQLITE_ROW != rc)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int attrnum = sqlite3_column_int(m_stmtAttrGetObj, 0);
|
|
|
|
|
const UTF8 *value = static_cast<const UTF8 *>(sqlite3_column_blob(m_stmtAttrGetObj, 1));
|
|
|
|
|
size_t len = static_cast<size_t>(sqlite3_column_bytes(m_stmtAttrGetObj, 1));
|
2026-03-05 08:09:38 -07:00
|
|
|
dbref owner = static_cast<dbref>(sqlite3_column_int(m_stmtAttrGetObj, 2));
|
|
|
|
|
int flags = sqlite3_column_int(m_stmtAttrGetObj, 3);
|
2026-03-04 21:23:24 -07:00
|
|
|
|
harden: fix five memory-safety/DoS bugs in database load paths
Audit of the DB load paths (flatfile reader, mail/malias loader, SQLite
attribute bulk-load) under the malicious/corrupt-database threat model --
the same class as #834-843. Five distinct bugs, all in code the June
hardening (#806/#808/#841/#843) did not reach; each is reachable only from
crafted or tampered database content, not from normal gameplay. Verified by
full rebuild + smoke suite (1264/1264, 0 crashes), which exercises flatfile
export/import and SQLite attribute reads end to end.
1. getstring_noalloc: static buffer overflow (lib/dbutil.cpp)
The escaped-string reader mis-accumulated its output-byte count:
`nOutput = pOutput - p` overwrote the count from prior escape emits
instead of adding to it. On the multi-fgets refill path this
under-decrements nBufferLeft, so the `nBufferLeft <= 0` guard never trips
and the next fgets writes past the 2*LBUF_SIZE+20 static buffer.
Legitimate attributes (<= LBUF) never take the refill path, so this only
fires on a crafted quoted string > ~64KB. Fix: accumulate (+=).
2. make_numlist / malias_read: stack buffer overflow (modules/engine/mail.cpp)
malias_read read the recipient count (numrecep) straight from the file
and pushed that many dbrefs into m->list with no cap, making
m->list.size() attacker-controlled. make_numlist then copied all of
m->list into the fixed stack array aRecip[(LBUF_SIZE+1)/2] with no bound
on nRecip -- a crafted mail.db with numrecep > 16384 overflows the stack
(with attacker-chosen dbrefs) the next time any player mails the alias.
Fix: clamp numrecep to (LBUF_SIZE+1)/2 at load (also bounds the reserve()
that could otherwise exhaust memory on an INT_MAX count), and defensively
bound the copy loop in make_numlist.
3. SQLite bulk-load attribute value: heap buffer overflow (modules/engine/sqlitedb.cpp)
GetAllAttributes/GetBuiltinAttributes passed the raw column blob length to
the cache with no clamp, unlike the write path (cache_put) and the
standalone read path (GetAttribute), both of which clamp to LBUF_SIZE. A
value blob written directly into the SQLite file therefore flows unclamped
to atr_get_str_LEN's `memcpy(s, buff, (*pLen)+1)` into a fixed LBUF_SIZE
buffer, overflowing the heap on first read of the attribute
(Name/look/examine/get). Fix: clamp len to LBUF_SIZE in both bulk-load
functions, mirroring the existing clamps.
4. get_list: infinite loop + unbounded log on truncated flatfile (modules/engine/db_rw.cpp)
get_list had no EOF case: at end-of-file getc() returns EOF, falls to
default, and calls getstring_noalloc(), which makes no progress at EOF
(ungetc(EOF) is a no-op, fgets returns NULL). The for(;;) then spins
forever, pegging a core and emitting log lines. Trigger: a flatfile whose
last object's attribute list is truncated before its '<' terminator. Fix:
add a case EOF that aborts the load.
5. getboolexp1: BOOLEXP subtree leak on malformed v2 lock (modules/engine/db_rw.cpp)
Three error paths in the v2 lock parser returned TRUE_BOOLEXP without
freeing the partially built node/subtree (bad connective, missing ')',
EOF mid lock-string). Bounded and single-shot (db_read aborts the whole
load on corruption; nesting capped at 1024), but still a leak on crafted
input. Fix: free the appropriate node/subtree at each site, matching the
partial-construction state.
Also traced and dropped a sixth candidate (SQLite attribute owner read
unvalidated): the attribute owner is never used to index db[] -- only
compared, read for its flags, or re-stored -- so a corrupt owner is harmless.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 19:52:53 -06:00
|
|
|
// Clamp a tampered/corrupt value blob to LBUF_SIZE, mirroring the
|
|
|
|
|
// write-path clamp (cache_put) and the standalone read clamp
|
|
|
|
|
// (GetAttribute). The consumer (atr_get_str_LEN) memcpy's this length
|
|
|
|
|
// into a fixed LBUF_SIZE buffer; an oversized blob would overflow it.
|
|
|
|
|
//
|
|
|
|
|
if (len > LBUF_SIZE)
|
|
|
|
|
{
|
|
|
|
|
len = LBUF_SIZE;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-05 17:03:35 -06:00
|
|
|
// sqlite3_column_blob() returns NULL with bytes > 0 only under OOM; skip
|
|
|
|
|
// such a row rather than handing the consumer a NULL it will dereference.
|
|
|
|
|
if (NULL == value && 0 != len)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 08:09:38 -07:00
|
|
|
cb(attrnum, value, len, owner, flags);
|
2026-03-04 21:23:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sqlite3_reset(m_stmtAttrGetObj);
|
2026-03-05 15:31:54 -07:00
|
|
|
if (SQLITE_DONE != rc)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::GetAllAttributes(#%d): %s\n", obj, sqlite3_errmsg(m_db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-04 21:23:24 -07:00
|
|
|
m_stats.attr_bulk_loads++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 08:34:03 -07:00
|
|
|
bool CSQLiteDB::GetBuiltinAttributes(dbref obj, AttrCallback cb)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtAttrGetBuiltin, 1, obj);
|
|
|
|
|
|
2026-03-05 15:31:54 -07:00
|
|
|
int rc = SQLITE_DONE;
|
2026-03-05 08:34:03 -07:00
|
|
|
for (;;)
|
|
|
|
|
{
|
2026-03-05 15:31:54 -07:00
|
|
|
rc = sqlite3_step(m_stmtAttrGetBuiltin);
|
2026-03-05 08:34:03 -07:00
|
|
|
if (SQLITE_ROW != rc)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int attrnum = sqlite3_column_int(m_stmtAttrGetBuiltin, 0);
|
|
|
|
|
const UTF8 *value = static_cast<const UTF8 *>(sqlite3_column_blob(m_stmtAttrGetBuiltin, 1));
|
|
|
|
|
size_t len = static_cast<size_t>(sqlite3_column_bytes(m_stmtAttrGetBuiltin, 1));
|
|
|
|
|
dbref owner = static_cast<dbref>(sqlite3_column_int(m_stmtAttrGetBuiltin, 2));
|
|
|
|
|
int flags = sqlite3_column_int(m_stmtAttrGetBuiltin, 3);
|
|
|
|
|
|
harden: fix five memory-safety/DoS bugs in database load paths
Audit of the DB load paths (flatfile reader, mail/malias loader, SQLite
attribute bulk-load) under the malicious/corrupt-database threat model --
the same class as #834-843. Five distinct bugs, all in code the June
hardening (#806/#808/#841/#843) did not reach; each is reachable only from
crafted or tampered database content, not from normal gameplay. Verified by
full rebuild + smoke suite (1264/1264, 0 crashes), which exercises flatfile
export/import and SQLite attribute reads end to end.
1. getstring_noalloc: static buffer overflow (lib/dbutil.cpp)
The escaped-string reader mis-accumulated its output-byte count:
`nOutput = pOutput - p` overwrote the count from prior escape emits
instead of adding to it. On the multi-fgets refill path this
under-decrements nBufferLeft, so the `nBufferLeft <= 0` guard never trips
and the next fgets writes past the 2*LBUF_SIZE+20 static buffer.
Legitimate attributes (<= LBUF) never take the refill path, so this only
fires on a crafted quoted string > ~64KB. Fix: accumulate (+=).
2. make_numlist / malias_read: stack buffer overflow (modules/engine/mail.cpp)
malias_read read the recipient count (numrecep) straight from the file
and pushed that many dbrefs into m->list with no cap, making
m->list.size() attacker-controlled. make_numlist then copied all of
m->list into the fixed stack array aRecip[(LBUF_SIZE+1)/2] with no bound
on nRecip -- a crafted mail.db with numrecep > 16384 overflows the stack
(with attacker-chosen dbrefs) the next time any player mails the alias.
Fix: clamp numrecep to (LBUF_SIZE+1)/2 at load (also bounds the reserve()
that could otherwise exhaust memory on an INT_MAX count), and defensively
bound the copy loop in make_numlist.
3. SQLite bulk-load attribute value: heap buffer overflow (modules/engine/sqlitedb.cpp)
GetAllAttributes/GetBuiltinAttributes passed the raw column blob length to
the cache with no clamp, unlike the write path (cache_put) and the
standalone read path (GetAttribute), both of which clamp to LBUF_SIZE. A
value blob written directly into the SQLite file therefore flows unclamped
to atr_get_str_LEN's `memcpy(s, buff, (*pLen)+1)` into a fixed LBUF_SIZE
buffer, overflowing the heap on first read of the attribute
(Name/look/examine/get). Fix: clamp len to LBUF_SIZE in both bulk-load
functions, mirroring the existing clamps.
4. get_list: infinite loop + unbounded log on truncated flatfile (modules/engine/db_rw.cpp)
get_list had no EOF case: at end-of-file getc() returns EOF, falls to
default, and calls getstring_noalloc(), which makes no progress at EOF
(ungetc(EOF) is a no-op, fgets returns NULL). The for(;;) then spins
forever, pegging a core and emitting log lines. Trigger: a flatfile whose
last object's attribute list is truncated before its '<' terminator. Fix:
add a case EOF that aborts the load.
5. getboolexp1: BOOLEXP subtree leak on malformed v2 lock (modules/engine/db_rw.cpp)
Three error paths in the v2 lock parser returned TRUE_BOOLEXP without
freeing the partially built node/subtree (bad connective, missing ')',
EOF mid lock-string). Bounded and single-shot (db_read aborts the whole
load on corruption; nesting capped at 1024), but still a leak on crafted
input. Fix: free the appropriate node/subtree at each site, matching the
partial-construction state.
Also traced and dropped a sixth candidate (SQLite attribute owner read
unvalidated): the attribute owner is never used to index db[] -- only
compared, read for its flags, or re-stored -- so a corrupt owner is harmless.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 19:52:53 -06:00
|
|
|
// Clamp a tampered/corrupt value blob to LBUF_SIZE, mirroring the
|
|
|
|
|
// write-path clamp (cache_put) and the standalone read clamp
|
|
|
|
|
// (GetAttribute). The consumer (atr_get_str_LEN) memcpy's this length
|
|
|
|
|
// into a fixed LBUF_SIZE buffer; an oversized blob would overflow it.
|
|
|
|
|
//
|
|
|
|
|
if (len > LBUF_SIZE)
|
|
|
|
|
{
|
|
|
|
|
len = LBUF_SIZE;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-05 17:03:35 -06:00
|
|
|
// sqlite3_column_blob() returns NULL with bytes > 0 only under OOM; skip
|
|
|
|
|
// such a row rather than handing the consumer a NULL it will dereference.
|
|
|
|
|
if (NULL == value && 0 != len)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 08:34:03 -07:00
|
|
|
cb(attrnum, value, len, owner, flags);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sqlite3_reset(m_stmtAttrGetBuiltin);
|
2026-03-05 15:31:54 -07:00
|
|
|
if (SQLITE_DONE != rc)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::GetBuiltinAttributes(#%d): %s\n", obj, sqlite3_errmsg(m_db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-05 08:34:03 -07:00
|
|
|
m_stats.attr_bulk_loads++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 21:54:56 -07:00
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Attribute name registry
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::PutAttrName(int attrnum, const char *name, int flags)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtAttrNamePut, 1, attrnum);
|
|
|
|
|
sqlite3_bind_text(m_stmtAttrNamePut, 2, name, -1, SQLITE_STATIC);
|
|
|
|
|
sqlite3_bind_int(m_stmtAttrNamePut, 3, flags);
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_step(m_stmtAttrNamePut);
|
|
|
|
|
sqlite3_reset(m_stmtAttrNamePut);
|
|
|
|
|
|
|
|
|
|
return SQLITE_DONE == rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::DelAttrName(int attrnum)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtAttrNameDel, 1, attrnum);
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_step(m_stmtAttrNameDel);
|
|
|
|
|
sqlite3_reset(m_stmtAttrNameDel);
|
|
|
|
|
|
|
|
|
|
return SQLITE_DONE == rc;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 15:42:21 -07:00
|
|
|
bool CSQLiteDB::ClearAttrNames()
|
|
|
|
|
{
|
|
|
|
|
return SQLITE_OK == sqlite3_exec(m_db, "DELETE FROM attrnames;", nullptr, nullptr, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 21:54:56 -07:00
|
|
|
bool CSQLiteDB::LoadAllAttrNames(AttrNameCallback cb)
|
|
|
|
|
{
|
2026-03-05 15:11:15 -07:00
|
|
|
int rc = SQLITE_DONE;
|
2026-03-04 21:54:56 -07:00
|
|
|
for (;;)
|
|
|
|
|
{
|
2026-03-05 15:11:15 -07:00
|
|
|
rc = sqlite3_step(m_stmtAttrNameLoadAll);
|
2026-03-04 21:54:56 -07:00
|
|
|
if (SQLITE_ROW != rc)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int attrnum = sqlite3_column_int(m_stmtAttrNameLoadAll, 0);
|
|
|
|
|
const char *name = reinterpret_cast<const char *>(
|
|
|
|
|
sqlite3_column_text(m_stmtAttrNameLoadAll, 1));
|
|
|
|
|
int flags = sqlite3_column_int(m_stmtAttrNameLoadAll, 2);
|
|
|
|
|
|
2026-06-05 17:03:35 -06:00
|
|
|
// The consumer treats name as a valid C string; a NULL text column
|
|
|
|
|
// (OOM, or an unexpected NULL row) would crash it. Skip such rows.
|
|
|
|
|
if (NULL == name)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 21:54:56 -07:00
|
|
|
cb(attrnum, name, flags);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sqlite3_reset(m_stmtAttrNameLoadAll);
|
2026-03-05 15:11:15 -07:00
|
|
|
if (SQLITE_DONE != rc)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::LoadAllAttrNames: %s\n", sqlite3_errmsg(m_db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-04 21:54:56 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Metadata key-value store
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::PutMeta(const char *key, int value)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_text(m_stmtMetaPut, 1, key, -1, SQLITE_STATIC);
|
|
|
|
|
sqlite3_bind_int(m_stmtMetaPut, 2, value);
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_step(m_stmtMetaPut);
|
|
|
|
|
sqlite3_reset(m_stmtMetaPut);
|
|
|
|
|
|
|
|
|
|
return SQLITE_DONE == rc;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 15:40:10 -07:00
|
|
|
CSQLiteDB::MetaGetResult CSQLiteDB::GetMetaEx(const char *key, int *value)
|
2026-03-04 21:54:56 -07:00
|
|
|
{
|
|
|
|
|
sqlite3_bind_text(m_stmtMetaGet, 1, key, -1, SQLITE_STATIC);
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_step(m_stmtMetaGet);
|
|
|
|
|
if (SQLITE_ROW == rc)
|
|
|
|
|
{
|
|
|
|
|
*value = sqlite3_column_int(m_stmtMetaGet, 0);
|
|
|
|
|
sqlite3_reset(m_stmtMetaGet);
|
2026-03-05 15:40:10 -07:00
|
|
|
return MetaGetResult::Found;
|
2026-03-04 21:54:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sqlite3_reset(m_stmtMetaGet);
|
|
|
|
|
*value = 0;
|
2026-03-05 15:40:10 -07:00
|
|
|
if (SQLITE_DONE == rc)
|
|
|
|
|
{
|
|
|
|
|
return MetaGetResult::NotFound;
|
|
|
|
|
}
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::GetMeta(%s): %s\n", key, sqlite3_errmsg(m_db));
|
|
|
|
|
return MetaGetResult::Error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::GetMeta(const char *key, int *value)
|
|
|
|
|
{
|
|
|
|
|
return MetaGetResult::Found == GetMetaEx(key, value);
|
2026-03-04 21:54:56 -07:00
|
|
|
}
|
|
|
|
|
|
2026-03-04 21:23:24 -07:00
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Transaction support
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::Begin()
|
|
|
|
|
{
|
|
|
|
|
return SQLITE_OK == sqlite3_exec(m_db, "BEGIN", nullptr, nullptr, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::Commit()
|
|
|
|
|
{
|
|
|
|
|
return SQLITE_OK == sqlite3_exec(m_db, "COMMIT", nullptr, nullptr, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::Rollback()
|
|
|
|
|
{
|
|
|
|
|
return SQLITE_OK == sqlite3_exec(m_db, "ROLLBACK", nullptr, nullptr, nullptr);
|
|
|
|
|
}
|
2026-03-04 21:08:06 -07:00
|
|
|
|
2026-03-05 08:58:32 -07:00
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Search queries
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
// Helper: prepare an ad-hoc search query, bind params, iterate results,
|
|
|
|
|
// call back with each matching dbref, then finalize. Search queries are
|
|
|
|
|
// infrequent (once per @search command), so the prepare/finalize overhead
|
|
|
|
|
// is negligible.
|
|
|
|
|
//
|
|
|
|
|
static bool RunSearch(sqlite3 *db, const char *sql,
|
|
|
|
|
const std::function<void(sqlite3_stmt *)> &bind,
|
|
|
|
|
CSQLiteDB::SearchCallback cb)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_stmt *stmt = nullptr;
|
|
|
|
|
if (SQLITE_OK != sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bind(stmt);
|
|
|
|
|
|
2026-03-05 15:31:54 -07:00
|
|
|
int rc = SQLITE_DONE;
|
2026-03-05 08:58:32 -07:00
|
|
|
for (;;)
|
|
|
|
|
{
|
2026-03-05 15:31:54 -07:00
|
|
|
rc = sqlite3_step(stmt);
|
2026-03-05 08:58:32 -07:00
|
|
|
if (SQLITE_ROW != rc)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
cb(static_cast<dbref>(sqlite3_column_int(stmt, 0)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sqlite3_finalize(stmt);
|
2026-03-05 15:31:54 -07:00
|
|
|
return SQLITE_DONE == rc;
|
2026-03-05 08:58:32 -07:00
|
|
|
}
|
|
|
|
|
|
2026-03-10 00:00:30 -06:00
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Connection log operations
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
int64_t CSQLiteDB::ConnlogInsert(dbref player, int64_t connect_time,
|
|
|
|
|
const UTF8 *host, const UTF8 *ipaddr)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_reset(m_stmtConnlogInsert);
|
|
|
|
|
sqlite3_bind_int(m_stmtConnlogInsert, 1, player);
|
|
|
|
|
sqlite3_bind_int64(m_stmtConnlogInsert, 2, connect_time);
|
|
|
|
|
sqlite3_bind_text(m_stmtConnlogInsert, 3,
|
|
|
|
|
reinterpret_cast<const char *>(host), -1, SQLITE_TRANSIENT);
|
|
|
|
|
sqlite3_bind_text(m_stmtConnlogInsert, 4,
|
|
|
|
|
reinterpret_cast<const char *>(ipaddr), -1, SQLITE_TRANSIENT);
|
|
|
|
|
|
|
|
|
|
if (SQLITE_DONE != sqlite3_step(m_stmtConnlogInsert))
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::ConnlogInsert: %s\n",
|
|
|
|
|
sqlite3_errmsg(m_db));
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
return sqlite3_last_insert_rowid(m_db);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::ConnlogUpdate(int64_t id, int64_t disconnect_time,
|
|
|
|
|
const UTF8 *reason)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_reset(m_stmtConnlogUpdate);
|
|
|
|
|
sqlite3_bind_int64(m_stmtConnlogUpdate, 1, disconnect_time);
|
|
|
|
|
sqlite3_bind_text(m_stmtConnlogUpdate, 2,
|
|
|
|
|
reinterpret_cast<const char *>(reason), -1, SQLITE_TRANSIENT);
|
|
|
|
|
sqlite3_bind_int64(m_stmtConnlogUpdate, 3, id);
|
|
|
|
|
|
|
|
|
|
if (SQLITE_DONE != sqlite3_step(m_stmtConnlogUpdate))
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::ConnlogUpdate: %s\n",
|
|
|
|
|
sqlite3_errmsg(m_db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::ConnlogByPlayer(dbref player, int limit, ConnlogCallback cb)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_reset(m_stmtConnlogByPlayer);
|
|
|
|
|
sqlite3_bind_int(m_stmtConnlogByPlayer, 1, player);
|
|
|
|
|
sqlite3_bind_int(m_stmtConnlogByPlayer, 2, limit);
|
|
|
|
|
|
|
|
|
|
int rc;
|
|
|
|
|
while (SQLITE_ROW == (rc = sqlite3_step(m_stmtConnlogByPlayer)))
|
|
|
|
|
{
|
|
|
|
|
cb(sqlite3_column_int64(m_stmtConnlogByPlayer, 0),
|
|
|
|
|
sqlite3_column_int(m_stmtConnlogByPlayer, 1),
|
|
|
|
|
sqlite3_column_int64(m_stmtConnlogByPlayer, 2),
|
|
|
|
|
sqlite3_column_int64(m_stmtConnlogByPlayer, 3),
|
|
|
|
|
reinterpret_cast<const UTF8 *>(sqlite3_column_text(m_stmtConnlogByPlayer, 4)),
|
|
|
|
|
reinterpret_cast<const UTF8 *>(sqlite3_column_text(m_stmtConnlogByPlayer, 5)),
|
|
|
|
|
reinterpret_cast<const UTF8 *>(sqlite3_column_text(m_stmtConnlogByPlayer, 6)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (SQLITE_DONE != rc)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::ConnlogByPlayer: %s\n",
|
|
|
|
|
sqlite3_errmsg(m_db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::ConnlogByAddr(const UTF8 *ipaddr, int limit, ConnlogCallback cb)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_reset(m_stmtConnlogByAddr);
|
|
|
|
|
sqlite3_bind_text(m_stmtConnlogByAddr, 1,
|
|
|
|
|
reinterpret_cast<const char *>(ipaddr), -1, SQLITE_TRANSIENT);
|
|
|
|
|
sqlite3_bind_int(m_stmtConnlogByAddr, 2, limit);
|
|
|
|
|
|
|
|
|
|
int rc;
|
|
|
|
|
while (SQLITE_ROW == (rc = sqlite3_step(m_stmtConnlogByAddr)))
|
|
|
|
|
{
|
|
|
|
|
cb(sqlite3_column_int64(m_stmtConnlogByAddr, 0),
|
|
|
|
|
sqlite3_column_int(m_stmtConnlogByAddr, 1),
|
|
|
|
|
sqlite3_column_int64(m_stmtConnlogByAddr, 2),
|
|
|
|
|
sqlite3_column_int64(m_stmtConnlogByAddr, 3),
|
|
|
|
|
reinterpret_cast<const UTF8 *>(sqlite3_column_text(m_stmtConnlogByAddr, 4)),
|
|
|
|
|
reinterpret_cast<const UTF8 *>(sqlite3_column_text(m_stmtConnlogByAddr, 5)),
|
|
|
|
|
reinterpret_cast<const UTF8 *>(sqlite3_column_text(m_stmtConnlogByAddr, 6)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (SQLITE_DONE != rc)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::ConnlogByAddr: %s\n",
|
|
|
|
|
sqlite3_errmsg(m_db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-11 22:12:11 -06:00
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Code cache operations
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
2026-03-15 00:31:44 -06:00
|
|
|
bool CSQLiteDB::CodeCacheGet(const char *source_hash, int source_hash_len,
|
|
|
|
|
const char *blob_hash, int blob_hash_len,
|
2026-03-11 22:12:11 -06:00
|
|
|
CodeCacheRecord &rec)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_reset(m_stmtCodeCacheGet);
|
2026-03-15 00:31:44 -06:00
|
|
|
sqlite3_bind_text(m_stmtCodeCacheGet, 1, source_hash, source_hash_len,
|
|
|
|
|
SQLITE_TRANSIENT);
|
|
|
|
|
sqlite3_bind_text(m_stmtCodeCacheGet, 2, blob_hash, blob_hash_len,
|
|
|
|
|
SQLITE_TRANSIENT);
|
2026-03-11 22:12:11 -06:00
|
|
|
|
|
|
|
|
int rc = sqlite3_step(m_stmtCodeCacheGet);
|
|
|
|
|
if (SQLITE_ROW != rc)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_reset(m_stmtCodeCacheGet);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rec.memory_blob = sqlite3_column_blob(m_stmtCodeCacheGet, 0);
|
|
|
|
|
rec.memory_len = sqlite3_column_bytes(m_stmtCodeCacheGet, 0);
|
2026-04-04 10:01:45 -06:00
|
|
|
rec.code_blob = sqlite3_column_blob(m_stmtCodeCacheGet, 1);
|
|
|
|
|
rec.code_len = sqlite3_column_bytes(m_stmtCodeCacheGet, 1);
|
|
|
|
|
rec.entry_pc = sqlite3_column_int64(m_stmtCodeCacheGet, 2);
|
|
|
|
|
rec.code_size = sqlite3_column_int64(m_stmtCodeCacheGet, 3);
|
|
|
|
|
rec.str_blob = sqlite3_column_blob(m_stmtCodeCacheGet, 4);
|
|
|
|
|
rec.str_len = sqlite3_column_bytes(m_stmtCodeCacheGet, 4);
|
|
|
|
|
rec.str_pool_end = sqlite3_column_int64(m_stmtCodeCacheGet, 5);
|
|
|
|
|
rec.fargs_blob = sqlite3_column_blob(m_stmtCodeCacheGet, 6);
|
|
|
|
|
rec.fargs_len = sqlite3_column_bytes(m_stmtCodeCacheGet, 6);
|
|
|
|
|
rec.fargs_pool_end = sqlite3_column_int64(m_stmtCodeCacheGet, 7);
|
|
|
|
|
rec.out_pool_end = sqlite3_column_int64(m_stmtCodeCacheGet, 8);
|
|
|
|
|
rec.out_addr = sqlite3_column_int64(m_stmtCodeCacheGet, 9);
|
|
|
|
|
rec.needs_jit = sqlite3_column_int(m_stmtCodeCacheGet, 10);
|
|
|
|
|
rec.folds = sqlite3_column_int(m_stmtCodeCacheGet, 11);
|
|
|
|
|
rec.ecalls = sqlite3_column_int(m_stmtCodeCacheGet, 12);
|
|
|
|
|
rec.tier2_calls = sqlite3_column_int(m_stmtCodeCacheGet, 13);
|
|
|
|
|
rec.native_ops = sqlite3_column_int(m_stmtCodeCacheGet, 14);
|
|
|
|
|
rec.deps_blob = sqlite3_column_blob(m_stmtCodeCacheGet, 15);
|
|
|
|
|
rec.deps_len = sqlite3_column_bytes(m_stmtCodeCacheGet, 15);
|
2026-07-21 14:55:28 -06:00
|
|
|
rec.max_func_depth = sqlite3_column_int64(m_stmtCodeCacheGet, 16);
|
2026-07-23 20:00:47 -06:00
|
|
|
rec.n_func_calls = sqlite3_column_int64(m_stmtCodeCacheGet, 17);
|
2026-04-04 16:34:54 -06:00
|
|
|
|
|
|
|
|
if ( (rec.memory_len > 0 && !rec.memory_blob)
|
|
|
|
|
|| (rec.code_len > 0 && !rec.code_blob)
|
|
|
|
|
|| (rec.str_len > 0 && !rec.str_blob)
|
|
|
|
|
|| (rec.fargs_len > 0 && !rec.fargs_blob)
|
|
|
|
|
|| (rec.deps_len > 0 && !rec.deps_blob))
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::CodeCacheGet: invalid NULL blob column\n");
|
|
|
|
|
sqlite3_reset(m_stmtCodeCacheGet);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 22:07:39 -06:00
|
|
|
// NOTE: memory_blob and deps_blob pointers are valid until next
|
|
|
|
|
// sqlite3_reset. Caller must copy before any further DB operations.
|
2026-03-11 22:12:11 -06:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-15 00:31:44 -06:00
|
|
|
bool CSQLiteDB::CodeCachePut(const char *source_hash, int source_hash_len,
|
|
|
|
|
const char *blob_hash, int blob_hash_len,
|
2026-03-11 22:12:11 -06:00
|
|
|
const void *memory_blob, int memory_len,
|
2026-04-04 10:01:45 -06:00
|
|
|
const void *code_blob, int code_len,
|
|
|
|
|
int64_t entry_pc, int64_t code_size,
|
|
|
|
|
const void *str_blob, int str_len, int64_t str_pool_end,
|
|
|
|
|
const void *fargs_blob, int fargs_len, int64_t fargs_pool_end,
|
|
|
|
|
int64_t out_pool_end,
|
2026-03-11 22:12:11 -06:00
|
|
|
int64_t out_addr, int needs_jit,
|
|
|
|
|
int folds, int ecalls, int tier2_calls,
|
2026-03-20 22:07:39 -06:00
|
|
|
int native_ops,
|
2026-07-21 14:55:28 -06:00
|
|
|
int64_t max_func_depth,
|
2026-07-23 20:00:47 -06:00
|
|
|
int64_t n_func_calls,
|
2026-03-20 22:07:39 -06:00
|
|
|
const void *deps_blob, int deps_len)
|
2026-03-11 22:12:11 -06:00
|
|
|
{
|
|
|
|
|
sqlite3_reset(m_stmtCodeCachePut);
|
2026-03-15 00:31:44 -06:00
|
|
|
sqlite3_bind_text(m_stmtCodeCachePut, 1, source_hash, source_hash_len,
|
|
|
|
|
SQLITE_TRANSIENT);
|
|
|
|
|
sqlite3_bind_text(m_stmtCodeCachePut, 2, blob_hash, blob_hash_len,
|
|
|
|
|
SQLITE_TRANSIENT);
|
2026-04-04 10:01:45 -06:00
|
|
|
BindBlob(m_stmtCodeCachePut, 3, memory_blob, memory_len);
|
|
|
|
|
BindBlob(m_stmtCodeCachePut, 4, code_blob, code_len);
|
|
|
|
|
sqlite3_bind_int64(m_stmtCodeCachePut, 5, entry_pc);
|
|
|
|
|
sqlite3_bind_int64(m_stmtCodeCachePut, 6, code_size);
|
|
|
|
|
BindBlob(m_stmtCodeCachePut, 7, str_blob, str_len);
|
|
|
|
|
sqlite3_bind_int64(m_stmtCodeCachePut, 8, str_pool_end);
|
|
|
|
|
BindBlob(m_stmtCodeCachePut, 9, fargs_blob, fargs_len);
|
|
|
|
|
sqlite3_bind_int64(m_stmtCodeCachePut, 10, fargs_pool_end);
|
|
|
|
|
sqlite3_bind_int64(m_stmtCodeCachePut, 11, out_pool_end);
|
|
|
|
|
sqlite3_bind_int64(m_stmtCodeCachePut, 12, out_addr);
|
|
|
|
|
sqlite3_bind_int(m_stmtCodeCachePut, 13, needs_jit);
|
|
|
|
|
sqlite3_bind_int(m_stmtCodeCachePut, 14, folds);
|
|
|
|
|
sqlite3_bind_int(m_stmtCodeCachePut, 15, ecalls);
|
|
|
|
|
sqlite3_bind_int(m_stmtCodeCachePut, 16, tier2_calls);
|
|
|
|
|
sqlite3_bind_int(m_stmtCodeCachePut, 17, native_ops);
|
|
|
|
|
sqlite3_bind_int64(m_stmtCodeCachePut, 18, static_cast<int64_t>(time(nullptr)));
|
|
|
|
|
BindBlob(m_stmtCodeCachePut, 19, deps_blob, deps_len);
|
2026-07-21 14:55:28 -06:00
|
|
|
sqlite3_bind_int64(m_stmtCodeCachePut, 20, max_func_depth);
|
2026-07-23 20:00:47 -06:00
|
|
|
sqlite3_bind_int64(m_stmtCodeCachePut, 21, n_func_calls);
|
2026-03-11 22:12:11 -06:00
|
|
|
|
|
|
|
|
if (SQLITE_DONE != sqlite3_step(m_stmtCodeCachePut))
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::CodeCachePut: %s\n",
|
|
|
|
|
sqlite3_errmsg(m_db));
|
2026-04-04 16:30:44 -06:00
|
|
|
sqlite3_reset(m_stmtCodeCachePut);
|
2026-03-11 22:12:11 -06:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::CodeCacheFlush()
|
|
|
|
|
{
|
2026-06-05 17:03:35 -06:00
|
|
|
// Reset *after* stepping so an error return (SQLITE_IOERR, SQLITE_FULL, ...)
|
|
|
|
|
// does not leave the statement holding error/lock state for the next use.
|
|
|
|
|
int rc = sqlite3_step(m_stmtCodeCacheFlush);
|
2026-03-11 22:12:11 -06:00
|
|
|
sqlite3_reset(m_stmtCodeCacheFlush);
|
2026-06-05 17:03:35 -06:00
|
|
|
return SQLITE_DONE == rc;
|
2026-03-11 22:12:11 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CSQLiteDB::CodeCacheReset()
|
|
|
|
|
{
|
|
|
|
|
sqlite3_reset(m_stmtCodeCacheGet);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 00:00:30 -06:00
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Search operations
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
2026-03-05 08:58:32 -07:00
|
|
|
bool CSQLiteDB::SearchByOwner(dbref owner, int type, dbref low, dbref high,
|
|
|
|
|
SearchCallback cb)
|
|
|
|
|
{
|
|
|
|
|
if (type != -1)
|
|
|
|
|
{
|
|
|
|
|
// Owner + type filter.
|
|
|
|
|
//
|
|
|
|
|
return RunSearch(m_db,
|
|
|
|
|
"SELECT dbref FROM objects"
|
|
|
|
|
" WHERE owner=? AND (flags1 & 7)=? AND (flags1 & 16384)=0"
|
|
|
|
|
" AND dbref BETWEEN ? AND ?"
|
|
|
|
|
" ORDER BY dbref",
|
|
|
|
|
[owner, type, low, high](sqlite3_stmt *s)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(s, 1, owner);
|
|
|
|
|
sqlite3_bind_int(s, 2, type);
|
|
|
|
|
sqlite3_bind_int(s, 3, low);
|
|
|
|
|
sqlite3_bind_int(s, 4, high);
|
|
|
|
|
}, cb);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return RunSearch(m_db,
|
|
|
|
|
"SELECT dbref FROM objects"
|
|
|
|
|
" WHERE owner=? AND (flags1 & 16384)=0"
|
|
|
|
|
" AND dbref BETWEEN ? AND ?"
|
|
|
|
|
" ORDER BY dbref",
|
|
|
|
|
[owner, low, high](sqlite3_stmt *s)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(s, 1, owner);
|
|
|
|
|
sqlite3_bind_int(s, 2, low);
|
|
|
|
|
sqlite3_bind_int(s, 3, high);
|
|
|
|
|
}, cb);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::SearchByType(int type, dbref low, dbref high, SearchCallback cb)
|
|
|
|
|
{
|
|
|
|
|
return RunSearch(m_db,
|
|
|
|
|
"SELECT dbref FROM objects"
|
|
|
|
|
" WHERE (flags1 & 7)=? AND (flags1 & 16384)=0"
|
|
|
|
|
" AND dbref BETWEEN ? AND ?"
|
|
|
|
|
" ORDER BY dbref",
|
|
|
|
|
[type, low, high](sqlite3_stmt *s)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(s, 1, type);
|
|
|
|
|
sqlite3_bind_int(s, 2, low);
|
|
|
|
|
sqlite3_bind_int(s, 3, high);
|
|
|
|
|
}, cb);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::SearchByZone(dbref zone, dbref low, dbref high, SearchCallback cb)
|
|
|
|
|
{
|
|
|
|
|
return RunSearch(m_db,
|
|
|
|
|
"SELECT dbref FROM objects"
|
|
|
|
|
" WHERE zone=? AND (flags1 & 16384)=0"
|
|
|
|
|
" AND dbref BETWEEN ? AND ?"
|
|
|
|
|
" ORDER BY dbref",
|
|
|
|
|
[zone, low, high](sqlite3_stmt *s)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(s, 1, zone);
|
|
|
|
|
sqlite3_bind_int(s, 2, low);
|
|
|
|
|
sqlite3_bind_int(s, 3, high);
|
|
|
|
|
}, cb);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::SearchByParent(dbref parent, dbref low, dbref high, SearchCallback cb)
|
|
|
|
|
{
|
|
|
|
|
return RunSearch(m_db,
|
|
|
|
|
"SELECT dbref FROM objects"
|
|
|
|
|
" WHERE parent=? AND (flags1 & 16384)=0"
|
|
|
|
|
" AND dbref BETWEEN ? AND ?"
|
|
|
|
|
" ORDER BY dbref",
|
|
|
|
|
[parent, low, high](sqlite3_stmt *s)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(s, 1, parent);
|
|
|
|
|
sqlite3_bind_int(s, 2, low);
|
|
|
|
|
sqlite3_bind_int(s, 3, high);
|
|
|
|
|
}, cb);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::SearchByFlags(FLAG f1, FLAG f2, FLAG f3,
|
|
|
|
|
dbref low, dbref high, SearchCallback cb)
|
|
|
|
|
{
|
|
|
|
|
// Build the query dynamically based on which flag words are non-zero.
|
|
|
|
|
// Always exclude GOING. The flags check is (flags & mask) == mask.
|
|
|
|
|
//
|
|
|
|
|
std::string sql = "SELECT dbref FROM objects WHERE (flags1 & 16384)=0";
|
|
|
|
|
|
|
|
|
|
if (f1)
|
|
|
|
|
{
|
|
|
|
|
sql += " AND (flags1 & ?) = ?";
|
|
|
|
|
}
|
|
|
|
|
if (f2)
|
|
|
|
|
{
|
|
|
|
|
sql += " AND (flags2 & ?) = ?";
|
|
|
|
|
}
|
|
|
|
|
if (f3)
|
|
|
|
|
{
|
|
|
|
|
sql += " AND (flags3 & ?) = ?";
|
|
|
|
|
}
|
|
|
|
|
sql += " AND dbref BETWEEN ? AND ? ORDER BY dbref";
|
|
|
|
|
|
|
|
|
|
return RunSearch(m_db, sql.c_str(),
|
|
|
|
|
[f1, f2, f3, low, high](sqlite3_stmt *s)
|
|
|
|
|
{
|
|
|
|
|
int p = 1;
|
|
|
|
|
if (f1)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(s, p++, static_cast<int>(f1));
|
|
|
|
|
sqlite3_bind_int(s, p++, static_cast<int>(f1));
|
|
|
|
|
}
|
|
|
|
|
if (f2)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(s, p++, static_cast<int>(f2));
|
|
|
|
|
sqlite3_bind_int(s, p++, static_cast<int>(f2));
|
|
|
|
|
}
|
|
|
|
|
if (f3)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(s, p++, static_cast<int>(f3));
|
|
|
|
|
sqlite3_bind_int(s, p++, static_cast<int>(f3));
|
|
|
|
|
}
|
|
|
|
|
sqlite3_bind_int(s, p++, low);
|
|
|
|
|
sqlite3_bind_int(s, p++, high);
|
|
|
|
|
}, cb);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 21:23:24 -07:00
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Maintenance
|
|
|
|
|
// ---------------------------------------------------------------------------
|
2026-03-04 21:08:06 -07:00
|
|
|
|
2026-03-04 21:23:24 -07:00
|
|
|
bool CSQLiteDB::Checkpoint()
|
|
|
|
|
{
|
2026-06-05 17:03:35 -06:00
|
|
|
// sqlite3_wal_checkpoint_v2() returns SQLITE_BUSY immediately if a reader
|
|
|
|
|
// (e.g. another connection under dbconvert -m) holds the WAL. Without a
|
|
|
|
|
// retry @dump would report success while the WAL keeps growing. The engine
|
|
|
|
|
// is single-threaded so contention is rare; a few short backoffs clear it.
|
|
|
|
|
const int kMaxAttempts = 5;
|
|
|
|
|
int rc = SQLITE_OK;
|
|
|
|
|
for (int attempt = 0; attempt < kMaxAttempts; ++attempt)
|
|
|
|
|
{
|
|
|
|
|
rc = sqlite3_wal_checkpoint_v2(m_db, nullptr,
|
|
|
|
|
SQLITE_CHECKPOINT_TRUNCATE, nullptr, nullptr);
|
|
|
|
|
if (SQLITE_BUSY != rc)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (attempt + 1 < kMaxAttempts)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_sleep(10 * (attempt + 1)); // 10, 20, 30, 40 ms backoff
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (SQLITE_OK != rc)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::Checkpoint: %s\n", sqlite3_errmsg(m_db));
|
|
|
|
|
}
|
2026-03-04 21:23:24 -07:00
|
|
|
return SQLITE_OK == rc;
|
|
|
|
|
}
|
2026-03-04 21:08:06 -07:00
|
|
|
|
2026-03-04 21:23:24 -07:00
|
|
|
bool CSQLiteDB::Optimize()
|
|
|
|
|
{
|
|
|
|
|
return SQLITE_OK == sqlite3_exec(m_db, "PRAGMA optimize", nullptr, nullptr, nullptr);
|
|
|
|
|
}
|
2026-03-04 23:42:39 -07:00
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Comsys bulk operations
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::ClearComsysTables()
|
|
|
|
|
{
|
|
|
|
|
return SQLITE_OK == sqlite3_exec(m_db,
|
|
|
|
|
"DELETE FROM channels; DELETE FROM channel_users; DELETE FROM player_channels;",
|
|
|
|
|
nullptr, nullptr, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::SyncChannel(const UTF8 *name, const UTF8 *header,
|
|
|
|
|
int type, int temp1, int temp2,
|
|
|
|
|
int charge, int charge_who, int amount_col,
|
|
|
|
|
int num_messages, int chan_obj)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_text(m_stmtChannelSync, 1, reinterpret_cast<const char *>(name), -1, SQLITE_STATIC);
|
|
|
|
|
sqlite3_bind_text(m_stmtChannelSync, 2, reinterpret_cast<const char *>(header), -1, SQLITE_STATIC);
|
|
|
|
|
sqlite3_bind_int(m_stmtChannelSync, 3, type);
|
|
|
|
|
sqlite3_bind_int(m_stmtChannelSync, 4, temp1);
|
|
|
|
|
sqlite3_bind_int(m_stmtChannelSync, 5, temp2);
|
|
|
|
|
sqlite3_bind_int(m_stmtChannelSync, 6, charge);
|
|
|
|
|
sqlite3_bind_int(m_stmtChannelSync, 7, charge_who);
|
|
|
|
|
sqlite3_bind_int(m_stmtChannelSync, 8, amount_col);
|
|
|
|
|
sqlite3_bind_int(m_stmtChannelSync, 9, num_messages);
|
|
|
|
|
sqlite3_bind_int(m_stmtChannelSync, 10, chan_obj);
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_step(m_stmtChannelSync);
|
|
|
|
|
sqlite3_reset(m_stmtChannelSync);
|
|
|
|
|
return SQLITE_DONE == rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::SyncChannelUser(const UTF8 *channel_name, int who,
|
|
|
|
|
bool is_on, bool comtitle_status,
|
|
|
|
|
bool gag_join_leave, const UTF8 *title)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_text(m_stmtChannelUserSync, 1, reinterpret_cast<const char *>(channel_name), -1, SQLITE_STATIC);
|
|
|
|
|
sqlite3_bind_int(m_stmtChannelUserSync, 2, who);
|
|
|
|
|
sqlite3_bind_int(m_stmtChannelUserSync, 3, is_on ? 1 : 0);
|
|
|
|
|
sqlite3_bind_int(m_stmtChannelUserSync, 4, comtitle_status ? 1 : 0);
|
|
|
|
|
sqlite3_bind_int(m_stmtChannelUserSync, 5, gag_join_leave ? 1 : 0);
|
|
|
|
|
sqlite3_bind_text(m_stmtChannelUserSync, 6, reinterpret_cast<const char *>(title), -1, SQLITE_STATIC);
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_step(m_stmtChannelUserSync);
|
|
|
|
|
sqlite3_reset(m_stmtChannelUserSync);
|
|
|
|
|
return SQLITE_DONE == rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::SyncPlayerChannel(int who, const UTF8 *alias,
|
|
|
|
|
const UTF8 *channel_name)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtPlayerChannelSync, 1, who);
|
|
|
|
|
sqlite3_bind_text(m_stmtPlayerChannelSync, 2, reinterpret_cast<const char *>(alias), -1, SQLITE_STATIC);
|
|
|
|
|
sqlite3_bind_text(m_stmtPlayerChannelSync, 3, reinterpret_cast<const char *>(channel_name), -1, SQLITE_STATIC);
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_step(m_stmtPlayerChannelSync);
|
|
|
|
|
sqlite3_reset(m_stmtPlayerChannelSync);
|
|
|
|
|
return SQLITE_DONE == rc;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 00:18:27 -07:00
|
|
|
bool CSQLiteDB::DeleteChannel(const UTF8 *name)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_text(m_stmtChannelDelete, 1, reinterpret_cast<const char *>(name), -1, SQLITE_STATIC);
|
|
|
|
|
int rc = sqlite3_step(m_stmtChannelDelete);
|
|
|
|
|
sqlite3_reset(m_stmtChannelDelete);
|
|
|
|
|
return SQLITE_DONE == rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::DeleteChannelUser(const UTF8 *channel_name, int who)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_text(m_stmtChannelUserDelete, 1, reinterpret_cast<const char *>(channel_name), -1, SQLITE_STATIC);
|
|
|
|
|
sqlite3_bind_int(m_stmtChannelUserDelete, 2, who);
|
|
|
|
|
int rc = sqlite3_step(m_stmtChannelUserDelete);
|
|
|
|
|
sqlite3_reset(m_stmtChannelUserDelete);
|
|
|
|
|
return SQLITE_DONE == rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::DeletePlayerChannel(int who, const UTF8 *alias)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtPlayerChannelDelete, 1, who);
|
|
|
|
|
sqlite3_bind_text(m_stmtPlayerChannelDelete, 2, reinterpret_cast<const char *>(alias), -1, SQLITE_STATIC);
|
|
|
|
|
int rc = sqlite3_step(m_stmtPlayerChannelDelete);
|
|
|
|
|
sqlite3_reset(m_stmtPlayerChannelDelete);
|
|
|
|
|
return SQLITE_DONE == rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::DeleteAllPlayerChannels(int who)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtPlayerChannelDeleteAll, 1, who);
|
|
|
|
|
int rc = sqlite3_step(m_stmtPlayerChannelDeleteAll);
|
|
|
|
|
sqlite3_reset(m_stmtPlayerChannelDeleteAll);
|
|
|
|
|
return SQLITE_DONE == rc;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 23:42:39 -07:00
|
|
|
bool CSQLiteDB::LoadAllChannels(ChannelCallback cb)
|
|
|
|
|
{
|
2026-03-05 15:20:32 -07:00
|
|
|
int rc = SQLITE_DONE;
|
2026-03-04 23:42:39 -07:00
|
|
|
for (;;)
|
|
|
|
|
{
|
2026-03-05 15:20:32 -07:00
|
|
|
rc = sqlite3_step(m_stmtChannelLoadAll);
|
2026-03-04 23:42:39 -07:00
|
|
|
if (SQLITE_ROW != rc)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const UTF8 *name = reinterpret_cast<const UTF8 *>(sqlite3_column_text(m_stmtChannelLoadAll, 0));
|
|
|
|
|
const UTF8 *header = reinterpret_cast<const UTF8 *>(sqlite3_column_text(m_stmtChannelLoadAll, 1));
|
|
|
|
|
int type = sqlite3_column_int(m_stmtChannelLoadAll, 2);
|
|
|
|
|
int temp1 = sqlite3_column_int(m_stmtChannelLoadAll, 3);
|
|
|
|
|
int temp2 = sqlite3_column_int(m_stmtChannelLoadAll, 4);
|
|
|
|
|
int charge = sqlite3_column_int(m_stmtChannelLoadAll, 5);
|
|
|
|
|
int charge_who = sqlite3_column_int(m_stmtChannelLoadAll, 6);
|
|
|
|
|
int amount_col = sqlite3_column_int(m_stmtChannelLoadAll, 7);
|
|
|
|
|
int num_messages = sqlite3_column_int(m_stmtChannelLoadAll, 8);
|
|
|
|
|
int chan_obj = sqlite3_column_int(m_stmtChannelLoadAll, 9);
|
|
|
|
|
|
|
|
|
|
cb(name ? name : reinterpret_cast<const UTF8 *>(""),
|
|
|
|
|
header ? header : reinterpret_cast<const UTF8 *>(""),
|
|
|
|
|
type, temp1, temp2, charge, charge_who, amount_col, num_messages, chan_obj);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sqlite3_reset(m_stmtChannelLoadAll);
|
2026-03-05 15:20:32 -07:00
|
|
|
if (SQLITE_DONE != rc)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::LoadAllChannels: %s\n", sqlite3_errmsg(m_db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-04 23:42:39 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::LoadAllChannelUsers(ChannelUserCallback cb)
|
|
|
|
|
{
|
2026-03-05 15:20:32 -07:00
|
|
|
int rc = SQLITE_DONE;
|
2026-03-04 23:42:39 -07:00
|
|
|
for (;;)
|
|
|
|
|
{
|
2026-03-05 15:20:32 -07:00
|
|
|
rc = sqlite3_step(m_stmtChannelUserLoadAll);
|
2026-03-04 23:42:39 -07:00
|
|
|
if (SQLITE_ROW != rc)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const UTF8 *channel_name = reinterpret_cast<const UTF8 *>(sqlite3_column_text(m_stmtChannelUserLoadAll, 0));
|
|
|
|
|
int who = sqlite3_column_int(m_stmtChannelUserLoadAll, 1);
|
|
|
|
|
bool is_on = sqlite3_column_int(m_stmtChannelUserLoadAll, 2) != 0;
|
|
|
|
|
bool comtitle = sqlite3_column_int(m_stmtChannelUserLoadAll, 3) != 0;
|
|
|
|
|
bool gag = sqlite3_column_int(m_stmtChannelUserLoadAll, 4) != 0;
|
|
|
|
|
const UTF8 *title = reinterpret_cast<const UTF8 *>(sqlite3_column_text(m_stmtChannelUserLoadAll, 5));
|
|
|
|
|
|
|
|
|
|
cb(channel_name ? channel_name : reinterpret_cast<const UTF8 *>(""),
|
|
|
|
|
who, is_on, comtitle, gag,
|
|
|
|
|
title ? title : reinterpret_cast<const UTF8 *>(""));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sqlite3_reset(m_stmtChannelUserLoadAll);
|
2026-03-05 15:20:32 -07:00
|
|
|
if (SQLITE_DONE != rc)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::LoadAllChannelUsers: %s\n", sqlite3_errmsg(m_db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-04 23:42:39 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::LoadAllPlayerChannels(PlayerChannelCallback cb)
|
|
|
|
|
{
|
2026-03-05 15:20:32 -07:00
|
|
|
int rc = SQLITE_DONE;
|
2026-03-04 23:42:39 -07:00
|
|
|
for (;;)
|
|
|
|
|
{
|
2026-03-05 15:20:32 -07:00
|
|
|
rc = sqlite3_step(m_stmtPlayerChannelLoadAll);
|
2026-03-04 23:42:39 -07:00
|
|
|
if (SQLITE_ROW != rc)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int who = sqlite3_column_int(m_stmtPlayerChannelLoadAll, 0);
|
|
|
|
|
const UTF8 *alias = reinterpret_cast<const UTF8 *>(sqlite3_column_text(m_stmtPlayerChannelLoadAll, 1));
|
|
|
|
|
const UTF8 *channel_name = reinterpret_cast<const UTF8 *>(sqlite3_column_text(m_stmtPlayerChannelLoadAll, 2));
|
|
|
|
|
|
|
|
|
|
cb(who,
|
|
|
|
|
alias ? alias : reinterpret_cast<const UTF8 *>(""),
|
|
|
|
|
channel_name ? channel_name : reinterpret_cast<const UTF8 *>(""));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sqlite3_reset(m_stmtPlayerChannelLoadAll);
|
2026-03-05 15:20:32 -07:00
|
|
|
if (SQLITE_DONE != rc)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::LoadAllPlayerChannels: %s\n", sqlite3_errmsg(m_db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-04 23:42:39 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Mail bulk operations
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
2026-03-05 00:26:24 -07:00
|
|
|
bool CSQLiteDB::ClearMailAliases()
|
|
|
|
|
{
|
|
|
|
|
return SQLITE_OK == sqlite3_exec(m_db,
|
|
|
|
|
"DELETE FROM mail_aliases;",
|
|
|
|
|
nullptr, nullptr, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 23:42:39 -07:00
|
|
|
bool CSQLiteDB::ClearMailTables()
|
|
|
|
|
{
|
|
|
|
|
return SQLITE_OK == sqlite3_exec(m_db,
|
|
|
|
|
"DELETE FROM mail_headers; DELETE FROM mail_bodies; DELETE FROM mail_aliases;",
|
|
|
|
|
nullptr, nullptr, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::SyncMailHeader(int to_player, int from_player, int body_number,
|
|
|
|
|
const UTF8 *tolist, const UTF8 *time_str,
|
|
|
|
|
const UTF8 *subject, int read_flags)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtMailHeaderSync, 1, to_player);
|
|
|
|
|
sqlite3_bind_int(m_stmtMailHeaderSync, 2, from_player);
|
|
|
|
|
sqlite3_bind_int(m_stmtMailHeaderSync, 3, body_number);
|
|
|
|
|
sqlite3_bind_text(m_stmtMailHeaderSync, 4, reinterpret_cast<const char *>(tolist), -1, SQLITE_STATIC);
|
|
|
|
|
sqlite3_bind_text(m_stmtMailHeaderSync, 5, reinterpret_cast<const char *>(time_str), -1, SQLITE_STATIC);
|
|
|
|
|
sqlite3_bind_text(m_stmtMailHeaderSync, 6, reinterpret_cast<const char *>(subject), -1, SQLITE_STATIC);
|
|
|
|
|
sqlite3_bind_int(m_stmtMailHeaderSync, 7, read_flags);
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_step(m_stmtMailHeaderSync);
|
|
|
|
|
sqlite3_reset(m_stmtMailHeaderSync);
|
|
|
|
|
return SQLITE_DONE == rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::SyncMailBody(int number, const UTF8 *message)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtMailBodySync, 1, number);
|
|
|
|
|
sqlite3_bind_text(m_stmtMailBodySync, 2, reinterpret_cast<const char *>(message), -1, SQLITE_STATIC);
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_step(m_stmtMailBodySync);
|
|
|
|
|
sqlite3_reset(m_stmtMailBodySync);
|
|
|
|
|
return SQLITE_DONE == rc;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 00:18:27 -07:00
|
|
|
int64_t CSQLiteDB::InsertMailHeaderReturningId(int to_player, int from_player,
|
|
|
|
|
int body_number, const UTF8 *tolist, const UTF8 *time_str,
|
|
|
|
|
const UTF8 *subject, int read_flags)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtMailHeaderInsertRet, 1, to_player);
|
|
|
|
|
sqlite3_bind_int(m_stmtMailHeaderInsertRet, 2, from_player);
|
|
|
|
|
sqlite3_bind_int(m_stmtMailHeaderInsertRet, 3, body_number);
|
|
|
|
|
sqlite3_bind_text(m_stmtMailHeaderInsertRet, 4, reinterpret_cast<const char *>(tolist), -1, SQLITE_STATIC);
|
|
|
|
|
sqlite3_bind_text(m_stmtMailHeaderInsertRet, 5, reinterpret_cast<const char *>(time_str), -1, SQLITE_STATIC);
|
|
|
|
|
sqlite3_bind_text(m_stmtMailHeaderInsertRet, 6, reinterpret_cast<const char *>(subject), -1, SQLITE_STATIC);
|
|
|
|
|
sqlite3_bind_int(m_stmtMailHeaderInsertRet, 7, read_flags);
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_step(m_stmtMailHeaderInsertRet);
|
|
|
|
|
sqlite3_reset(m_stmtMailHeaderInsertRet);
|
|
|
|
|
|
|
|
|
|
if (SQLITE_DONE == rc)
|
|
|
|
|
{
|
|
|
|
|
return sqlite3_last_insert_rowid(m_db);
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::UpdateMailReadFlags(int64_t rowid, int read_flags)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtMailHeaderUpdateFlags, 1, read_flags);
|
|
|
|
|
sqlite3_bind_int64(m_stmtMailHeaderUpdateFlags, 2, rowid);
|
|
|
|
|
int rc = sqlite3_step(m_stmtMailHeaderUpdateFlags);
|
|
|
|
|
sqlite3_reset(m_stmtMailHeaderUpdateFlags);
|
|
|
|
|
return SQLITE_DONE == rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::DeleteMailHeader(int64_t rowid)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int64(m_stmtMailHeaderDelete, 1, rowid);
|
|
|
|
|
int rc = sqlite3_step(m_stmtMailHeaderDelete);
|
|
|
|
|
sqlite3_reset(m_stmtMailHeaderDelete);
|
|
|
|
|
return SQLITE_DONE == rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::DeleteAllMailHeaders(int to_player)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtMailHeaderDeleteAll, 1, to_player);
|
|
|
|
|
int rc = sqlite3_step(m_stmtMailHeaderDeleteAll);
|
|
|
|
|
sqlite3_reset(m_stmtMailHeaderDeleteAll);
|
|
|
|
|
return SQLITE_DONE == rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::DeleteMailBody(int number)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtMailBodyDelete, 1, number);
|
|
|
|
|
int rc = sqlite3_step(m_stmtMailBodyDelete);
|
|
|
|
|
sqlite3_reset(m_stmtMailBodyDelete);
|
|
|
|
|
return SQLITE_DONE == rc;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-04 23:42:39 -07:00
|
|
|
bool CSQLiteDB::SyncMailAlias(int owner, const UTF8 *name, const UTF8 *desc,
|
|
|
|
|
int desc_width, const UTF8 *members)
|
|
|
|
|
{
|
|
|
|
|
sqlite3_bind_int(m_stmtMailAliasSync, 1, owner);
|
|
|
|
|
sqlite3_bind_text(m_stmtMailAliasSync, 2, reinterpret_cast<const char *>(name), -1, SQLITE_STATIC);
|
|
|
|
|
sqlite3_bind_text(m_stmtMailAliasSync, 3, reinterpret_cast<const char *>(desc), -1, SQLITE_STATIC);
|
|
|
|
|
sqlite3_bind_int(m_stmtMailAliasSync, 4, desc_width);
|
|
|
|
|
sqlite3_bind_text(m_stmtMailAliasSync, 5, reinterpret_cast<const char *>(members), -1, SQLITE_STATIC);
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_step(m_stmtMailAliasSync);
|
|
|
|
|
sqlite3_reset(m_stmtMailAliasSync);
|
|
|
|
|
return SQLITE_DONE == rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::LoadAllMailHeaders(MailHeaderCallback cb)
|
|
|
|
|
{
|
2026-03-05 15:20:32 -07:00
|
|
|
int rc = SQLITE_DONE;
|
2026-03-04 23:42:39 -07:00
|
|
|
for (;;)
|
|
|
|
|
{
|
2026-03-05 15:20:32 -07:00
|
|
|
rc = sqlite3_step(m_stmtMailHeaderLoadAll);
|
2026-03-04 23:42:39 -07:00
|
|
|
if (SQLITE_ROW != rc)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 00:18:27 -07:00
|
|
|
int64_t rowid = sqlite3_column_int64(m_stmtMailHeaderLoadAll, 0);
|
|
|
|
|
int to_player = sqlite3_column_int(m_stmtMailHeaderLoadAll, 1);
|
|
|
|
|
int from_player = sqlite3_column_int(m_stmtMailHeaderLoadAll, 2);
|
|
|
|
|
int body_number = sqlite3_column_int(m_stmtMailHeaderLoadAll, 3);
|
|
|
|
|
const UTF8 *tolist = reinterpret_cast<const UTF8 *>(sqlite3_column_text(m_stmtMailHeaderLoadAll, 4));
|
|
|
|
|
const UTF8 *time_str = reinterpret_cast<const UTF8 *>(sqlite3_column_text(m_stmtMailHeaderLoadAll, 5));
|
|
|
|
|
const UTF8 *subject = reinterpret_cast<const UTF8 *>(sqlite3_column_text(m_stmtMailHeaderLoadAll, 6));
|
|
|
|
|
int read_flags = sqlite3_column_int(m_stmtMailHeaderLoadAll, 7);
|
2026-03-04 23:42:39 -07:00
|
|
|
|
2026-03-05 00:18:27 -07:00
|
|
|
cb(rowid, to_player, from_player, body_number,
|
2026-03-04 23:42:39 -07:00
|
|
|
tolist ? tolist : reinterpret_cast<const UTF8 *>(""),
|
|
|
|
|
time_str ? time_str : reinterpret_cast<const UTF8 *>(""),
|
|
|
|
|
subject ? subject : reinterpret_cast<const UTF8 *>(""),
|
|
|
|
|
read_flags);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sqlite3_reset(m_stmtMailHeaderLoadAll);
|
2026-03-05 15:20:32 -07:00
|
|
|
if (SQLITE_DONE != rc)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::LoadAllMailHeaders: %s\n", sqlite3_errmsg(m_db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-04 23:42:39 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::LoadAllMailBodies(MailBodyCallback cb)
|
|
|
|
|
{
|
2026-03-05 15:20:32 -07:00
|
|
|
int rc = SQLITE_DONE;
|
2026-03-04 23:42:39 -07:00
|
|
|
for (;;)
|
|
|
|
|
{
|
2026-03-05 15:20:32 -07:00
|
|
|
rc = sqlite3_step(m_stmtMailBodyLoadAll);
|
2026-03-04 23:42:39 -07:00
|
|
|
if (SQLITE_ROW != rc)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int number = sqlite3_column_int(m_stmtMailBodyLoadAll, 0);
|
|
|
|
|
const UTF8 *message = reinterpret_cast<const UTF8 *>(sqlite3_column_text(m_stmtMailBodyLoadAll, 1));
|
|
|
|
|
|
|
|
|
|
cb(number, message ? message : reinterpret_cast<const UTF8 *>(""));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sqlite3_reset(m_stmtMailBodyLoadAll);
|
2026-03-05 15:20:32 -07:00
|
|
|
if (SQLITE_DONE != rc)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::LoadAllMailBodies: %s\n", sqlite3_errmsg(m_db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-04 23:42:39 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CSQLiteDB::LoadAllMailAliases(MailAliasCallback cb)
|
|
|
|
|
{
|
2026-03-05 15:20:32 -07:00
|
|
|
int rc = SQLITE_DONE;
|
2026-03-04 23:42:39 -07:00
|
|
|
for (;;)
|
|
|
|
|
{
|
2026-03-05 15:20:32 -07:00
|
|
|
rc = sqlite3_step(m_stmtMailAliasLoadAll);
|
2026-03-04 23:42:39 -07:00
|
|
|
if (SQLITE_ROW != rc)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int owner = sqlite3_column_int(m_stmtMailAliasLoadAll, 0);
|
|
|
|
|
const UTF8 *name = reinterpret_cast<const UTF8 *>(sqlite3_column_text(m_stmtMailAliasLoadAll, 1));
|
|
|
|
|
const UTF8 *desc = reinterpret_cast<const UTF8 *>(sqlite3_column_text(m_stmtMailAliasLoadAll, 2));
|
|
|
|
|
int desc_width = sqlite3_column_int(m_stmtMailAliasLoadAll, 3);
|
|
|
|
|
const UTF8 *members = reinterpret_cast<const UTF8 *>(sqlite3_column_text(m_stmtMailAliasLoadAll, 4));
|
|
|
|
|
|
|
|
|
|
cb(owner,
|
|
|
|
|
name ? name : reinterpret_cast<const UTF8 *>(""),
|
|
|
|
|
desc ? desc : reinterpret_cast<const UTF8 *>(""),
|
|
|
|
|
desc_width,
|
|
|
|
|
members ? members : reinterpret_cast<const UTF8 *>(""));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sqlite3_reset(m_stmtMailAliasLoadAll);
|
2026-03-05 15:20:32 -07:00
|
|
|
if (SQLITE_DONE != rc)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "CSQLiteDB::LoadAllMailAliases: %s\n", sqlite3_errmsg(m_db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-04 23:42:39 -07:00
|
|
|
return true;
|
|
|
|
|
}
|