tinymux/mux/modules/engine/sqlitedb.cpp
Stephen Dennis fb6362eb61 fix(comsys/mail): activate modules and sync softcode with module store (#1191)
Root cause: comsys_mod/mail_mod lacked mux_CanUnloadNow, so ModuleLoad
never set bLoaded and CreateInstance always returned CLASSNOTAVAILABLE —
commands and softcode both used the engine path by accident.

With modules actually loaded:
- Bump module revision on every write-through; softcode reloads engine
  maps from SQLite when the revision advances (select_channel, mail_fetch,
  MailList, etc.).
- CComsysStorage::SyncChannel sets has_comsys so softcode reload works.
- Channel Sync uses ON CONFLICT DO UPDATE so updates do not CASCADE-wipe
  channel_users/player_channels.
- mailsend() goes through IMailControl::SoftcodeSend; bodies and
  mail_db_top are write-through for softcode mail_* fields.
2026-07-25 08:33:42 -06:00

2682 lines
90 KiB
C++

/*! \file sqlitedb.cpp
* \brief SQLite storage backend for TinyMUX.
*
*/
#if !defined(TINYMUX_TYPES_DEFINED)
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"
#endif
#include "sqlitedb.h"
#include "engine_api.h"
#include <cstring>
#include <cstdio>
#include <ctime>
#include <string>
// ---------------------------------------------------------------------------
// 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),
m_stmtAttrGetModCount(nullptr),
m_stmtAttrDel(nullptr),
m_stmtAttrDelObj(nullptr),
m_stmtAttrGetObj(nullptr),
m_stmtAttrGetBuiltin(nullptr),
m_stmtAttrCount(nullptr),
m_stmtAttrNamePut(nullptr),
m_stmtAttrNameDel(nullptr),
m_stmtAttrNameLoadAll(nullptr),
m_stmtMetaPut(nullptr),
m_stmtMetaGet(nullptr),
m_stmtChannelSync(nullptr),
m_stmtChannelUserSync(nullptr),
m_stmtPlayerChannelSync(nullptr),
m_stmtChannelLoadAll(nullptr),
m_stmtChannelUserLoadAll(nullptr),
m_stmtPlayerChannelLoadAll(nullptr),
m_stmtChannelDelete(nullptr),
m_stmtChannelUserDelete(nullptr),
m_stmtPlayerChannelDelete(nullptr),
m_stmtPlayerChannelDeleteAll(nullptr),
m_stmtMailHeaderSync(nullptr),
m_stmtMailHeaderInsertRet(nullptr),
m_stmtMailHeaderUpdateFlags(nullptr),
m_stmtMailHeaderDelete(nullptr),
m_stmtMailHeaderDeleteAll(nullptr),
m_stmtMailBodySync(nullptr),
m_stmtMailBodyDelete(nullptr),
m_stmtMailAliasSync(nullptr),
m_stmtMailHeaderLoadAll(nullptr),
m_stmtMailBodyLoadAll(nullptr),
m_stmtMailAliasLoadAll(nullptr),
m_stmtConnlogInsert(nullptr),
m_stmtConnlogUpdate(nullptr),
m_stmtConnlogByPlayer(nullptr),
m_stmtConnlogByAddr(nullptr),
m_stmtCodeCacheGet(nullptr),
m_stmtCodeCachePut(nullptr),
m_stmtCodeCacheFlush(nullptr),
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;
}
if (!MigrateSchema())
{
Close();
return false;
}
if (!PrepareStatements())
{
Close();
return false;
}
// Remember the path we opened so callers can report exactly which file
// backs the database.
//
if (nullptr != path)
{
m_path.assign(path);
}
return true;
}
void CSQLiteDB::Close()
{
if (m_db)
{
FinalizeStatements();
sqlite3_close(m_db);
m_db = nullptr;
}
m_path.clear();
}
bool CSQLiteDB::ConfigurePragmas()
{
const char *pragmas[] =
{
"PRAGMA journal_mode=WAL",
"PRAGMA synchronous=NORMAL",
"PRAGMA busy_timeout=5000",
"PRAGMA mmap_size=268435456",
"PRAGMA page_size=4096",
"PRAGMA cache_size=-65536",
"PRAGMA foreign_keys=ON",
// 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",
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 =
// Core tables (no foreign key dependencies).
//
"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,"
" owner INTEGER NOT NULL DEFAULT -1,"
" flags INTEGER NOT NULL DEFAULT 0,"
" mod_count INTEGER NOT NULL DEFAULT 0,"
" PRIMARY KEY (object, attrnum)"
") 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"
") WITHOUT ROWID;"
// Comsys: channels first (referenced), then dependents with FKs.
//
"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 ("
" 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;"
"CREATE TABLE IF NOT EXISTS 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;"
// Mail: bodies first (referenced), then headers with FK.
//
"CREATE TABLE IF NOT EXISTS mail_bodies ("
" number INTEGER PRIMARY KEY,"
" message TEXT NOT NULL DEFAULT ''"
");"
"CREATE TABLE IF NOT EXISTS 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"
");"
"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 ''"
");"
// 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 ''"
");"
// 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);"
"CREATE INDEX IF NOT EXISTS idx_objects_parent ON objects(parent);"
"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);"
"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);"
// 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,"
" compile_time INTEGER NOT NULL DEFAULT 0"
");";
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;
}
// ---------------------------------------------------------------------------
// Schema migration
// ---------------------------------------------------------------------------
// 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);
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);
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;
}
bool CSQLiteDB::MigrateSchema()
{
static const int CURRENT_SCHEMA_VERSION = 13;
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;
}
// ---------------------------------------------------------------
// v0/v1 -> v2: add foreign keys and secondary indexes.
// ---------------------------------------------------------------
//
if (version < 2)
{
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;
}
// ---------------------------------------------------------------
// 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.
// ---------------------------------------------------------------
//
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;
}
// ---------------------------------------------------------------
// 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;
}
// ---------------------------------------------------------------
// v4 -> v5: remove legacy packed attr-253 rows.
// Attribute membership is derived relationally by (object, attrnum),
// so attr 253 is redundant and should not be persisted.
// ---------------------------------------------------------------
//
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;
}
// ---------------------------------------------------------------
// 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;
}
// ---------------------------------------------------------------
// 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,"
" compile_time INTEGER NOT NULL DEFAULT 0"
");"
"INSERT OR REPLACE INTO metadata(key, value)"
" VALUES('schema_version', 7);"
"COMMIT;";
if (!RunMigration(m_db, migration_v7, 7))
{
return false;
}
version = 7;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
// Log any FK violations (informational, not fatal).
//
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;
}
// ---------------------------------------------------------------------------
// 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;
}
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);
}
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,
"SELECT value, owner, flags FROM attributes WHERE object=? AND attrnum=?",
&m_stmtAttrGet))
{
return false;
}
if (!Prepare(m_db,
"INSERT INTO attributes (object, attrnum, value, owner, flags, mod_count)"
" VALUES (?,?,?,?,?,?)"
" ON CONFLICT(object, attrnum) DO UPDATE SET"
" value=excluded.value,"
" owner=excluded.owner,"
" flags=excluded.flags,"
" mod_count=MAX(excluded.mod_count, mod_count+1)",
&m_stmtAttrPut))
{
return false;
}
if (!Prepare(m_db,
"SELECT mod_count FROM attributes WHERE object=? AND attrnum=?",
&m_stmtAttrGetModCount))
{
return false;
}
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,
"SELECT attrnum, value, owner, flags FROM attributes WHERE object=? ORDER BY attrnum",
&m_stmtAttrGetObj))
{
return false;
}
if (!Prepare(m_db,
"SELECT attrnum, value, owner, flags FROM attributes WHERE object=? AND attrnum < 256 ORDER BY attrnum",
&m_stmtAttrGetBuiltin))
{
return false;
}
if (!Prepare(m_db,
"SELECT COUNT(*) FROM attributes WHERE object=?",
&m_stmtAttrCount))
{
return false;
}
// 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;
}
// Comsys statements.
//
// 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).
//
if (!Prepare(m_db,
"INSERT INTO channels "
"(name, header, type, temp1, temp2, charge, charge_who, amount_col, num_messages, chan_obj) "
"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",
&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;
}
// 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;
// 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;
}
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;
if (!Prepare(m_db,
"INSERT INTO mail_aliases (owner, name, description, desc_width, members) "
"VALUES (?,?,?,?,?)",
&m_stmtMailAliasSync))
{
return false;
}
if (!Prepare(m_db,
"SELECT rowid, to_player, from_player, body_number, tolist, time_str, subject, read_flags "
"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;
}
// 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;
}
// Code cache statements.
//
if (!Prepare(m_db,
"SELECT 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, deps_blob,"
" max_func_depth, n_func_calls"
" FROM code_cache WHERE source_hash=? AND blob_hash=?",
&m_stmtCodeCacheGet))
{
return false;
}
if (!Prepare(m_db,
"INSERT OR REPLACE INTO code_cache"
" (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,"
" compile_time, deps_blob, max_func_depth, n_func_calls)"
" VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
&m_stmtCodeCachePut))
{
return false;
}
if (!Prepare(m_db,
"DELETE FROM code_cache",
&m_stmtCodeCacheFlush))
{
return false;
}
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);
Finalize(&m_stmtAttrGetBuiltin);
Finalize(&m_stmtAttrCount);
Finalize(&m_stmtAttrNamePut);
Finalize(&m_stmtAttrNameDel);
Finalize(&m_stmtAttrNameLoadAll);
Finalize(&m_stmtMetaPut);
Finalize(&m_stmtMetaGet);
Finalize(&m_stmtChannelSync);
Finalize(&m_stmtChannelUserSync);
Finalize(&m_stmtPlayerChannelSync);
Finalize(&m_stmtChannelLoadAll);
Finalize(&m_stmtChannelUserLoadAll);
Finalize(&m_stmtPlayerChannelLoadAll);
Finalize(&m_stmtChannelDelete);
Finalize(&m_stmtChannelUserDelete);
Finalize(&m_stmtPlayerChannelDelete);
Finalize(&m_stmtPlayerChannelDeleteAll);
Finalize(&m_stmtMailHeaderSync);
Finalize(&m_stmtMailHeaderInsertRet);
Finalize(&m_stmtMailHeaderUpdateFlags);
Finalize(&m_stmtMailHeaderDelete);
Finalize(&m_stmtMailHeaderDeleteAll);
Finalize(&m_stmtMailBodySync);
Finalize(&m_stmtMailBodyDelete);
Finalize(&m_stmtMailAliasSync);
Finalize(&m_stmtMailHeaderLoadAll);
Finalize(&m_stmtMailBodyLoadAll);
Finalize(&m_stmtMailAliasLoadAll);
Finalize(&m_stmtConnlogInsert);
Finalize(&m_stmtConnlogUpdate);
Finalize(&m_stmtConnlogByPlayer);
Finalize(&m_stmtConnlogByAddr);
Finalize(&m_stmtCodeCacheGet);
Finalize(&m_stmtCodeCachePut);
Finalize(&m_stmtCodeCacheFlush);
}
// ---------------------------------------------------------------------------
// 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;
}
bool CSQLiteDB::ClearObjectTable()
{
return SQLITE_OK == sqlite3_exec(m_db, "DELETE FROM objects;", nullptr, nullptr, nullptr);
}
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)
{
int rc = SQLITE_DONE;
for (;;)
{
rc = sqlite3_step(m_stmtObjLoadAll);
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);
if (SQLITE_DONE != rc)
{
fprintf(stderr, "CSQLiteDB::LoadAllObjects: %s\n", sqlite3_errmsg(m_db));
return false;
}
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
// ---------------------------------------------------------------------------
bool CSQLiteDB::GetAttribute(dbref obj, int attrnum, UTF8 *buf, size_t buflen,
size_t *pLen, dbref *owner, int *flags)
{
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));
// 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;
}
if (blobLen > buflen)
{
blobLen = buflen;
}
if (0 != blobLen)
{
memcpy(buf, blob, blobLen);
}
*pLen = blobLen;
if (owner) *owner = static_cast<dbref>(sqlite3_column_int(m_stmtAttrGet, 1));
if (flags) *flags = sqlite3_column_int(m_stmtAttrGet, 2);
sqlite3_reset(m_stmtAttrGet);
m_stats.attr_gets++;
return true;
}
sqlite3_reset(m_stmtAttrGet);
*pLen = 0;
if (owner) *owner = NOTHING;
if (flags) *flags = 0;
return false;
}
bool CSQLiteDB::PutAttribute(dbref obj, int attrnum, const UTF8 *value, size_t len,
dbref owner, int flags)
{
// 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;
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);
sqlite3_bind_int(m_stmtAttrPut, 4, owner);
sqlite3_bind_int(m_stmtAttrPut, 5, flags);
sqlite3_bind_int(m_stmtAttrPut, 6, static_cast<int>(mc));
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;
}
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;
}
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);
}
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;
}
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;
}
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);
}
bool CSQLiteDB::ClearAttributes()
{
bool ok = SQLITE_OK == sqlite3_exec(m_db, "DELETE FROM attributes;", nullptr, nullptr, nullptr);
if (ok)
{
attr_mod_count_invalidate_all();
}
return ok;
}
bool CSQLiteDB::GetAllAttributes(dbref obj, AttrCallback cb)
{
sqlite3_bind_int(m_stmtAttrGetObj, 1, obj);
int rc = SQLITE_DONE;
for (;;)
{
rc = sqlite3_step(m_stmtAttrGetObj);
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));
dbref owner = static_cast<dbref>(sqlite3_column_int(m_stmtAttrGetObj, 2));
int flags = sqlite3_column_int(m_stmtAttrGetObj, 3);
// 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;
}
// 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;
}
cb(attrnum, value, len, owner, flags);
}
sqlite3_reset(m_stmtAttrGetObj);
if (SQLITE_DONE != rc)
{
fprintf(stderr, "CSQLiteDB::GetAllAttributes(#%d): %s\n", obj, sqlite3_errmsg(m_db));
return false;
}
m_stats.attr_bulk_loads++;
return true;
}
bool CSQLiteDB::GetBuiltinAttributes(dbref obj, AttrCallback cb)
{
sqlite3_bind_int(m_stmtAttrGetBuiltin, 1, obj);
int rc = SQLITE_DONE;
for (;;)
{
rc = sqlite3_step(m_stmtAttrGetBuiltin);
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);
// 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;
}
// 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;
}
cb(attrnum, value, len, owner, flags);
}
sqlite3_reset(m_stmtAttrGetBuiltin);
if (SQLITE_DONE != rc)
{
fprintf(stderr, "CSQLiteDB::GetBuiltinAttributes(#%d): %s\n", obj, sqlite3_errmsg(m_db));
return false;
}
m_stats.attr_bulk_loads++;
return true;
}
// ---------------------------------------------------------------------------
// 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;
}
bool CSQLiteDB::ClearAttrNames()
{
return SQLITE_OK == sqlite3_exec(m_db, "DELETE FROM attrnames;", nullptr, nullptr, nullptr);
}
bool CSQLiteDB::LoadAllAttrNames(AttrNameCallback cb)
{
int rc = SQLITE_DONE;
for (;;)
{
rc = sqlite3_step(m_stmtAttrNameLoadAll);
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);
// 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;
}
cb(attrnum, name, flags);
}
sqlite3_reset(m_stmtAttrNameLoadAll);
if (SQLITE_DONE != rc)
{
fprintf(stderr, "CSQLiteDB::LoadAllAttrNames: %s\n", sqlite3_errmsg(m_db));
return false;
}
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;
}
CSQLiteDB::MetaGetResult CSQLiteDB::GetMetaEx(const char *key, int *value)
{
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);
return MetaGetResult::Found;
}
sqlite3_reset(m_stmtMetaGet);
*value = 0;
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);
}
// ---------------------------------------------------------------------------
// 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);
}
// ---------------------------------------------------------------------------
// 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);
int rc = SQLITE_DONE;
for (;;)
{
rc = sqlite3_step(stmt);
if (SQLITE_ROW != rc)
{
break;
}
cb(static_cast<dbref>(sqlite3_column_int(stmt, 0)));
}
sqlite3_finalize(stmt);
return SQLITE_DONE == rc;
}
// ---------------------------------------------------------------------------
// 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;
}
// ---------------------------------------------------------------------------
// Code cache operations
// ---------------------------------------------------------------------------
bool CSQLiteDB::CodeCacheGet(const char *source_hash, int source_hash_len,
const char *blob_hash, int blob_hash_len,
CodeCacheRecord &rec)
{
sqlite3_reset(m_stmtCodeCacheGet);
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);
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);
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);
rec.max_func_depth = sqlite3_column_int64(m_stmtCodeCacheGet, 16);
rec.n_func_calls = sqlite3_column_int64(m_stmtCodeCacheGet, 17);
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;
}
// NOTE: memory_blob and deps_blob pointers are valid until next
// sqlite3_reset. Caller must copy before any further DB operations.
return true;
}
bool CSQLiteDB::CodeCachePut(const char *source_hash, int source_hash_len,
const char *blob_hash, int blob_hash_len,
const void *memory_blob, int memory_len,
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,
int64_t out_addr, int needs_jit,
int folds, int ecalls, int tier2_calls,
int native_ops,
int64_t max_func_depth,
int64_t n_func_calls,
const void *deps_blob, int deps_len)
{
sqlite3_reset(m_stmtCodeCachePut);
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);
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);
sqlite3_bind_int64(m_stmtCodeCachePut, 20, max_func_depth);
sqlite3_bind_int64(m_stmtCodeCachePut, 21, n_func_calls);
if (SQLITE_DONE != sqlite3_step(m_stmtCodeCachePut))
{
fprintf(stderr, "CSQLiteDB::CodeCachePut: %s\n",
sqlite3_errmsg(m_db));
sqlite3_reset(m_stmtCodeCachePut);
return false;
}
return true;
}
bool CSQLiteDB::CodeCacheFlush()
{
// 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);
sqlite3_reset(m_stmtCodeCacheFlush);
return SQLITE_DONE == rc;
}
void CSQLiteDB::CodeCacheReset()
{
sqlite3_reset(m_stmtCodeCacheGet);
}
// ---------------------------------------------------------------------------
// Search operations
// ---------------------------------------------------------------------------
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);
}
// ---------------------------------------------------------------------------
// Maintenance
// ---------------------------------------------------------------------------
bool CSQLiteDB::Checkpoint()
{
// 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));
}
return SQLITE_OK == rc;
}
bool CSQLiteDB::Optimize()
{
return SQLITE_OK == sqlite3_exec(m_db, "PRAGMA optimize", nullptr, nullptr, nullptr);
}
// ---------------------------------------------------------------------------
// 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;
}
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;
}
bool CSQLiteDB::LoadAllChannels(ChannelCallback cb)
{
int rc = SQLITE_DONE;
for (;;)
{
rc = sqlite3_step(m_stmtChannelLoadAll);
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);
if (SQLITE_DONE != rc)
{
fprintf(stderr, "CSQLiteDB::LoadAllChannels: %s\n", sqlite3_errmsg(m_db));
return false;
}
return true;
}
bool CSQLiteDB::LoadAllChannelUsers(ChannelUserCallback cb)
{
int rc = SQLITE_DONE;
for (;;)
{
rc = sqlite3_step(m_stmtChannelUserLoadAll);
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);
if (SQLITE_DONE != rc)
{
fprintf(stderr, "CSQLiteDB::LoadAllChannelUsers: %s\n", sqlite3_errmsg(m_db));
return false;
}
return true;
}
bool CSQLiteDB::LoadAllPlayerChannels(PlayerChannelCallback cb)
{
int rc = SQLITE_DONE;
for (;;)
{
rc = sqlite3_step(m_stmtPlayerChannelLoadAll);
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);
if (SQLITE_DONE != rc)
{
fprintf(stderr, "CSQLiteDB::LoadAllPlayerChannels: %s\n", sqlite3_errmsg(m_db));
return false;
}
return true;
}
// ---------------------------------------------------------------------------
// Mail bulk operations
// ---------------------------------------------------------------------------
bool CSQLiteDB::ClearMailAliases()
{
return SQLITE_OK == sqlite3_exec(m_db,
"DELETE FROM mail_aliases;",
nullptr, nullptr, nullptr);
}
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;
}
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;
}
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)
{
int rc = SQLITE_DONE;
for (;;)
{
rc = sqlite3_step(m_stmtMailHeaderLoadAll);
if (SQLITE_ROW != rc)
{
break;
}
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);
cb(rowid, to_player, from_player, body_number,
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);
if (SQLITE_DONE != rc)
{
fprintf(stderr, "CSQLiteDB::LoadAllMailHeaders: %s\n", sqlite3_errmsg(m_db));
return false;
}
return true;
}
bool CSQLiteDB::LoadAllMailBodies(MailBodyCallback cb)
{
int rc = SQLITE_DONE;
for (;;)
{
rc = sqlite3_step(m_stmtMailBodyLoadAll);
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);
if (SQLITE_DONE != rc)
{
fprintf(stderr, "CSQLiteDB::LoadAllMailBodies: %s\n", sqlite3_errmsg(m_db));
return false;
}
return true;
}
bool CSQLiteDB::LoadAllMailAliases(MailAliasCallback cb)
{
int rc = SQLITE_DONE;
for (;;)
{
rc = sqlite3_step(m_stmtMailAliasLoadAll);
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);
if (SQLITE_DONE != rc)
{
fprintf(stderr, "CSQLiteDB::LoadAllMailAliases: %s\n", sqlite3_errmsg(m_db));
return false;
}
return true;
}