Expand description
Persistent data for a bot, in one SQLite file.
This module needs the store feature.
A Store is one open SQLite database. A bot gives each part of itself
(for example, each group of handlers) its own Namespace. A namespace
keeps data in two ways:
- Key/value data.
Namespace::setkeeps a value of each type that implementsserde::Serialize, as JSON.Namespace::getdecodes it into a type that implementsserde::Deserialize.Namespace::deleteandNamespace::keyscomplete the set. This needs no schema. - SQL tables.
Namespace::sqlruns a closure with the realrusqlite::Connection. There is no query layer between the closure and SQLite.Namespace::migrateapplies a list of schema changes. The store records the version of each namespace, so each step runs one time only.
SQLite calls block the thread. All these functions run the call on Tokio’s blocking pool, so a slow query does not stop the runtime.
§Key/value example
use ircbot::store::Store;
let store = Store::memory()?;
let karma = store.namespace("karma")?;
karma.set("alice", &3_i64).await?;
let score: Option<i64> = karma.get("alice").await?;
assert_eq!(score, Some(3));A value of a #[derive(Serialize, Deserialize)] struct works the same way.
If the struct gets a new field later, give the field #[serde(default)],
so a value stored before the change still decodes.
§SQL example
use ircbot::store::Store;
// A real bot gives a file path to `Store::open`.
let store = Store::memory()?;
let quotes = store.namespace("quotes")?;
// Add new steps at the end of the list. Do not change or remove a step
// that a database already has.
quotes
.migrate(&["CREATE TABLE quotes_quote (id INTEGER PRIMARY KEY, text TEXT NOT NULL)"])
.await?;
quotes
.sql(|conn| conn.execute("INSERT INTO quotes_quote (text) VALUES (?1)", ["hello"]))
.await?;
let count: i64 = quotes
.sql(|conn| conn.query_row("SELECT count(*) FROM quotes_quote", [], |row| row.get(0)))
.await?;
assert_eq!(count, 1);To use a store in handlers, put the namespace in the bot state. An open
database has no useful Default, so use no_default and start the bot
with new_with_state. The sqlite_bot example of this crate is a
complete bot that does this:
struct Data { quotes: Namespace }
#[bot(state = Data, no_default)]
impl QuoteBot { /* … */ }
let store = Store::open("bot.db")?;
let quotes = store.namespace("quotes")?;
quotes.migrate(QUOTE_MIGRATIONS).await?;
QuoteBot::new_with_state("quotebot", "irc.example.net:6667", ["#rust"], Data { quotes })
.main_loop()
.await§Names
A namespace name starts with a lowercase ASCII letter. The other characters
are lowercase ASCII letters, digits, and _. Thus a name is safe to use in
SQL as a prefix of a table name.
SQLite keeps table names that start with sqlite_ for its own use. Thus a
namespace cannot have the name sqlite, or a name that starts with
sqlite_.
All namespaces share one database. Give each table the name of its
namespace as a prefix (quotes_quote), so two namespaces do not use the
same table. The store cannot make sure of this, because sql gives the
full connection.
The store keeps its own data in tables whose names start with _ircbot.
Do not change these tables.
Re-exports§
pub use rusqlite;
Structs§
Enums§
- Store
Error - An error from the store.