Initial SQLx implementation (variables). not yet wired up to bot.
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details

- Adds migrations for the necessary tables.
 - Implements the user variables database functions.
 - Adds sqlx metadata for 'offline' use so we can build without a database.
This commit is contained in:
projectmoon 2021-05-15 15:27:40 +00:00
parent b1972e2850
commit 6b6e59da2e
17 changed files with 855 additions and 2 deletions

2
.env Normal file
View File

@ -0,0 +1,2 @@
DATABASE_URL="sqlite://test-db/dicebot.sqlite"
SQLX_OFFLINE="true"

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ bot-db*
# We store a disabled async test in this file
bigboy
.#*
*.sqlite

434
Cargo.lock generated
View File

@ -66,6 +66,23 @@ dependencies = [
"opaque-debug",
]
[[package]]
name = "ahash"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
[[package]]
name = "ahash"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "796540673305a66d127804eef19ad696f1f204b8c1025aaca4958c17eab32877"
dependencies = [
"getrandom 0.2.2",
"once_cell",
"version_check",
]
[[package]]
name = "aho-corasick"
version = "0.7.18"
@ -98,6 +115,15 @@ dependencies = [
"syn",
]
[[package]]
name = "atoi"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5"
dependencies = [
"num-traits",
]
[[package]]
name = "atomic"
version = "0.5.0"
@ -138,6 +164,12 @@ dependencies = [
"tokio",
]
[[package]]
name = "barrel"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d67c978b1322c8031145b1f6c236fc371292f52c565bc96018b2971afcbffe1"
[[package]]
name = "base-x"
version = "0.2.8"
@ -165,6 +197,18 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "bitvec"
version = "0.19.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
@ -174,6 +218,12 @@ dependencies = [
"generic-array",
]
[[package]]
name = "build_const"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7"
[[package]]
name = "bumpalo"
version = "3.6.1"
@ -227,6 +277,19 @@ dependencies = [
"zeroize",
]
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits",
"time 0.1.43",
"winapi",
]
[[package]]
name = "cipher"
version = "0.2.5"
@ -292,6 +355,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"
[[package]]
name = "crc"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb"
dependencies = [
"build_const",
]
[[package]]
name = "crc32fast"
version = "1.2.1"
@ -301,6 +373,16 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.4"
@ -314,6 +396,16 @@ dependencies = [
"scopeguard",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f6cb3c7f5b8e51bc3ebb73a2327ad4abdbd119dc13223f14f961d2f38486756"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.4"
@ -389,11 +481,20 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]]
name = "dotenv"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
dependencies = [
"serde",
]
[[package]]
name = "encoding_rs"
@ -417,6 +518,18 @@ dependencies = [
"termcolor",
]
[[package]]
name = "fallible-iterator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fnv"
version = "1.0.7"
@ -458,6 +571,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "funty"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
[[package]]
name = "futf"
version = "0.1.4"
@ -671,6 +790,27 @@ name = "hashbrown"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
dependencies = [
"ahash 0.4.7",
]
[[package]]
name = "hashlink"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d99cf782f0dc4372d26846bec3de7804ceb5df083c2d4462c0b8d2330e894fa8"
dependencies = [
"hashbrown",
]
[[package]]
name = "heck"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
@ -681,6 +821,12 @@ dependencies = [
"libc",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hmac"
version = "0.10.1"
@ -901,6 +1047,17 @@ version = "0.2.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
[[package]]
name = "libsqlite3-sys"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64d31059f22935e6c31830db5249ba2b7ecd54fd73a9909286f0a67aa55c2fbd"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "lock_api"
version = "0.4.4"
@ -1136,6 +1293,19 @@ dependencies = [
"version_check",
]
[[package]]
name = "nom"
version = "6.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
dependencies = [
"bitvec",
"funty",
"lexical-core",
"memchr",
"version_check",
]
[[package]]
name = "ntapi"
version = "0.3.6"
@ -1145,6 +1315,25 @@ dependencies = [
"winapi",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
@ -1433,6 +1622,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
[[package]]
name = "rand"
version = "0.7.3"
@ -1543,6 +1738,50 @@ dependencies = [
"redox_syscall",
]
[[package]]
name = "refinery"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e29bd9c881127d714f4b5b9fdd9ea7651f3dd254922e959a10f6ada620e841da"
dependencies = [
"refinery-core",
"refinery-macros",
]
[[package]]
name = "refinery-core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53260bc01535ea10c553ce0fc410609ba2dc0a9f4c9b4503e0af842dd4a6f89d"
dependencies = [
"async-trait",
"cfg-if",
"chrono",
"lazy_static",
"log",
"regex",
"rusqlite",
"serde",
"siphasher",
"thiserror",
"toml",
"url",
"walkdir",
]
[[package]]
name = "refinery-macros"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a79ff62c9b674b62c06a09cc8becf06cbafba9952afa1d8174e7e15f2c4ed43"
dependencies = [
"proc-macro2",
"quote",
"refinery-core",
"regex",
"syn",
]
[[package]]
name = "regex"
version = "1.5.4"
@ -1775,6 +2014,21 @@ dependencies = [
"syn",
]
[[package]]
name = "rusqlite"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f38ee71cbab2c827ec0ac24e76f82eca723cee92c509a65f67dee393c25112"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"memchr",
"smallvec",
]
[[package]]
name = "rustc_version"
version = "0.2.3"
@ -1790,6 +2044,15 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.19"
@ -1876,6 +2139,7 @@ version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
@ -1965,6 +2229,105 @@ dependencies = [
"winapi",
]
[[package]]
name = "sqlformat"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d86e3c77ff882a828346ba401a7ef4b8e440df804491c6064fe8295765de71c"
dependencies = [
"lazy_static",
"maplit",
"nom 6.1.2",
"regex",
"unicode_categories",
]
[[package]]
name = "sqlx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2739d54a2ae9fdd0f545cb4e4b5574efb95e2ec71b7f921678e246fb20dcaaf"
dependencies = [
"sqlx-core",
"sqlx-macros",
]
[[package]]
name = "sqlx-core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1cad9cae4ca8947eba1a90e8ec7d3c59e7a768e2f120dc9013b669c34a90711"
dependencies = [
"ahash 0.6.3",
"atoi",
"bitflags",
"byteorder",
"bytes",
"crc",
"crossbeam-channel",
"crossbeam-queue",
"crossbeam-utils",
"either",
"futures-channel",
"futures-core",
"futures-util",
"hashlink",
"hex",
"itoa",
"libc",
"libsqlite3-sys",
"log",
"memchr",
"once_cell",
"parking_lot",
"percent-encoding",
"serde",
"sha2",
"smallvec",
"sqlformat",
"sqlx-rt",
"stringprep",
"thiserror",
"tokio-stream",
"url",
"whoami",
]
[[package]]
name = "sqlx-macros"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01caee2b3935b4efe152f3262afbe51546ce3b1fc27ad61014e1b3cf5f55366e"
dependencies = [
"dotenv",
"either",
"futures",
"heck",
"hex",
"once_cell",
"proc-macro2",
"quote",
"serde",
"serde_json",
"sha2",
"sqlx-core",
"sqlx-rt",
"syn",
"url",
]
[[package]]
name = "sqlx-rt"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ce2e16b6774c671cc183e1d202386fdf9cde1e8468c1894a7f2a63eb671c4f4"
dependencies = [
"native-tls",
"once_cell",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "standback"
version = "0.2.17"
@ -2054,6 +2417,16 @@ dependencies = [
"quote",
]
[[package]]
name = "stringprep"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "subtle"
version = "2.4.0"
@ -2083,6 +2456,12 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tempfile"
version = "3.2.0"
@ -2113,6 +2492,7 @@ name = "tenebrous-dicebot"
version = "0.10.0"
dependencies = [
"async-trait",
"barrel",
"bincode",
"byteorder",
"combine",
@ -2125,11 +2505,13 @@ dependencies = [
"log",
"matrix-sdk",
"memmem",
"nom",
"nom 5.1.2",
"phf",
"rand 0.8.3",
"refinery",
"serde",
"sled",
"sqlx",
"thiserror",
"tokio",
"toml",
@ -2270,6 +2652,17 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8864d706fdb3cc0843a49647ac892720dac98a6eeb818b77190592cf4994066"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.6.7"
@ -2371,6 +2764,12 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
[[package]]
name = "unicode-width"
version = "0.1.8"
@ -2383,6 +2782,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "unicode_categories"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "unindent"
version = "0.1.7"
@ -2439,6 +2844,17 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "walkdir"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
[[package]]
name = "want"
version = "0.3.0"
@ -2539,6 +2955,16 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "whoami"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4abacf325c958dfeaf1046931d37f2a901b6dfe0968ee965a29e94c6766b2af6"
dependencies = [
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wildmatch"
version = "2.1.0"
@ -2585,6 +3011,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "wyz"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
[[package]]
name = "xml5ever"
version = "0.16.1"

View File

@ -10,6 +10,10 @@ repository = 'https://git.agnos.is/projectmoon/matrix-dicebot'
keywords = ["games", "dice", "matrix", "bot"]
categories = ["games"]
[[bin]]
name = "dicebot-migrate"
path = "src/migrate_cli.rs"
[dependencies]
log = "0.4"
env_logger = "0.8"
@ -32,6 +36,12 @@ bincode = "1.3"
html2text = "0.2"
phf = { version = "0.8", features = ["macros"] }
matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk", branch = "master" }
refinery = { version = "0.5", features = ["rusqlite"]}
barrel = { version = "0.6", features = ["sqlite3"] }
[dependencies.sqlx]
version = "0.5"
features = [ "offline", "sqlite", "runtime-tokio-native-tls" ]
[dependencies.serde]
version = "1"

45
sqlx-data.json Normal file
View File

@ -0,0 +1,45 @@
{
"db": "SQLite",
"636b1b868eaf04cd234fbf17747d94a66e81f7bc1b060ba14151dbfaf40eeefc": {
"query": "SELECT value as \"value: i32\" FROM user_variables\n WHERE user_id = ? AND room_id = ? AND key = ?",
"describe": {
"columns": [
{
"name": "value: i32",
"ordinal": 0,
"type_info": "Int64"
}
],
"parameters": {
"Right": 3
},
"nullable": [
false
]
}
},
"d6558668b7395b95ded8da71c80963ddde957abdcc3c68b03431f8e904e0d21f": {
"query": "SELECT key, value as \"value: i32\" FROM user_variables\n WHERE room_id = ?",
"describe": {
"columns": [
{
"name": "key",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "value: i32",
"ordinal": 1,
"type_info": "Int64"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false
]
}
}
}

View File

@ -7,6 +7,7 @@ use tenebrous_dicebot::bot::DiceBot;
use tenebrous_dicebot::config::*;
use tenebrous_dicebot::db::Database;
use tenebrous_dicebot::error::BotError;
use tenebrous_dicebot::migrator;
use tenebrous_dicebot::state::DiceBotState;
#[tokio::main]
@ -33,6 +34,9 @@ async fn run() -> Result<(), BotError> {
db.migrate(cfg.migration_version())?;
let sqlite_path = format!("{}/dicebot.sqlite", cfg.database_path());
migrator::migrate(&sqlite_path).await?;
match DiceBot::new(&cfg, &state, &db) {
Ok(bot) => bot.run().await?,
Err(e) => println!("Error connecting: {:?}", e),

View File

@ -12,6 +12,7 @@ pub mod errors;
pub mod migrations;
pub mod rooms;
pub mod schema;
pub mod sqlite;
pub mod state;
pub mod variables;

81
src/db/sqlite/errors.rs Normal file
View File

@ -0,0 +1,81 @@
use sled::transaction::{TransactionError, UnabortableTransactionError};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MigrationError {
#[error("cannot downgrade to an older database version")]
CannotDowngrade,
#[error("migration for version {0} not defined")]
MigrationNotFound(u32),
#[error("migration failed: {0}")]
MigrationFailed(String),
}
//TODO better combining of key and value in certain errors (namely
//I32SchemaViolation).
#[derive(Error, Debug)]
pub enum DataError {
#[error("value does not exist for key: {0}")]
KeyDoesNotExist(String),
#[error("too many entries")]
TooManyEntries,
#[error("expected i32, but i32 schema was violated")]
I32SchemaViolation,
#[error("unexpected or corruptd data bytes")]
InvalidValue,
#[error("expected string ref, but utf8 schema was violated: {0}")]
Utf8RefSchemaViolation(#[from] std::str::Utf8Error),
#[error("expected string, but utf8 schema was violated: {0}")]
Utf8SchemaViolation(#[from] std::string::FromUtf8Error),
#[error("internal database error: {0}")]
InternalError(#[from] sled::Error),
#[error("transaction error: {0}")]
TransactionError(#[from] sled::transaction::TransactionError),
#[error("unabortable transaction error: {0}")]
UnabortableTransactionError(#[from] UnabortableTransactionError),
#[error("data migration error: {0}")]
MigrationError(#[from] MigrationError),
#[error("deserialization error: {0}")]
DeserializationError(#[from] bincode::Error),
#[error("sqlx error: {0}")]
SqlxError(#[from] sqlx::Error),
}
/// This From implementation is necessary to deal with the recursive
/// error type in the error enum. We defined a transaction error, but
/// the only place we use it is when converting from
/// sled::transaction::TransactionError<DataError>. This converter
/// extracts the inner data error from transaction aborted errors, and
/// forwards anything else onward as-is, but wrapped in DataError.
impl From<TransactionError<DataError>> for DataError {
fn from(error: TransactionError<DataError>) -> Self {
match error {
TransactionError::Abort(data_err) => data_err,
TransactionError::Storage(storage_err) => {
DataError::TransactionError(TransactionError::Storage(storage_err))
}
}
}
}
/// Automatically aborts transactions that hit a DataError by using
/// the try (question mark) operator when this trait implementation is
/// in scope.
impl From<DataError> for sled::transaction::ConflictableTransactionError<DataError> {
fn from(error: DataError) -> Self {
sled::transaction::ConflictableTransactionError::Abort(error)
}
}

80
src/db/sqlite/mod.rs Normal file
View File

@ -0,0 +1,80 @@
use async_trait::async_trait;
use errors::DataError;
use sqlx::sqlite::{SqliteConnectOptions, SqlitePool, SqlitePoolOptions};
use sqlx::ConnectOptions;
use sqlx::Connection;
use std::collections::HashMap;
use std::path::Path;
use std::str::FromStr;
pub mod errors;
pub mod variables;
// TODO move this up to the top once we delete sled. Traits will be the
// main API, then we can have different impls for different DBs.
#[async_trait]
pub(crate) trait Variables {
async fn get_user_variables(
&self,
user: &str,
room_id: &str,
) -> Result<HashMap<String, i32>, DataError>;
async fn get_variable_count(&self, user: &str, room_id: &str) -> Result<i32, DataError>;
async fn get_user_variable(
&self,
user: &str,
room_id: &str,
variable_name: &str,
) -> Result<i32, DataError>;
async fn set_user_variable(
&self,
user: &str,
room_id: &str,
variable_name: &str,
value: i32,
) -> Result<(), DataError>;
async fn delete_user_variable(
&self,
user: &str,
room_id: &str,
variable_name: &str,
) -> Result<(), DataError>;
}
pub struct Database {
conn: SqlitePool,
}
impl Database {
fn new_db(conn: SqlitePool) -> Result<Database, DataError> {
let database = Database { conn: conn.clone() };
Ok(database)
}
pub async fn new(path: &str) -> Result<Database, DataError> {
//Create database if missing.
let conn = SqliteConnectOptions::from_str(path)?
.create_if_missing(true)
.connect()
.await?;
drop(conn);
//Return actual conncetion pool.
let conn = SqlitePoolOptions::new()
.max_connections(5)
.connect(path)
.await?;
Self::new_db(conn)
}
pub async fn new_temp() -> Result<Database, DataError> {
Self::new("sqlite::memory:").await
}
}

View File

@ -0,0 +1,92 @@
use super::errors::DataError;
use super::{Database, Variables};
use async_trait::async_trait;
use std::collections::HashMap;
struct UserVariableRow {
key: String,
value: i32,
}
#[async_trait]
impl Variables for Database {
async fn get_user_variables(
&self,
user: &str,
room_id: &str,
) -> Result<HashMap<String, i32>, DataError> {
let rows = sqlx::query!(
r#"SELECT key, value as "value: i32" FROM user_variables
WHERE room_id = ?"#,
room_id,
)
.fetch_all(&self.conn)
.await?;
Ok(rows.into_iter().map(|row| (row.key, row.value)).collect())
}
async fn get_variable_count(&self, user: &str, room_id: &str) -> Result<i32, DataError> {
Ok(1)
}
async fn get_user_variable(
&self,
user: &str,
room_id: &str,
variable_name: &str,
) -> Result<i32, DataError> {
let row = sqlx::query!(
r#"SELECT value as "value: i32" FROM user_variables
WHERE user_id = ? AND room_id = ? AND key = ?"#,
user,
room_id,
variable_name
)
.fetch_one(&self.conn)
.await?;
Ok(row.value)
}
async fn set_user_variable(
&self,
user: &str,
room_id: &str,
variable_name: &str,
value: i32,
) -> Result<(), DataError> {
sqlx::query(
"INSERT INTO user_variables
(user_id, room_id, variable_name, value)
values (?, ?, ?, ?)",
)
.bind(user)
.bind(room_id)
.bind(variable_name)
.bind(value)
.execute(&self.conn)
.await?;
Ok(())
}
async fn delete_user_variable(
&self,
user: &str,
room_id: &str,
variable_name: &str,
) -> Result<(), DataError> {
sqlx::query(
"DELETE FROM user_variables
WHERE user_id = ? AND room_id = ? AND variable_name = ?",
)
.bind(user)
.bind(room_id)
.bind(variable_name)
.execute(&self.conn)
.await?;
Ok(())
}
}

View File

@ -1,6 +1,6 @@
use crate::commands::CommandError;
use crate::config::ConfigError;
use crate::db::errors::DataError;
use crate::{commands::CommandError, migrator::migrations};
use thiserror::Error;
#[derive(Error, Debug)]
@ -73,6 +73,9 @@ pub enum BotError {
#[error("database error")]
DatabaseError(#[from] sled::Error),
#[error("database migration error: {0}")]
SqliteError(#[from] crate::migrator::MigrationError),
#[error("too many commands or message was too large")]
MessageTooLarge,

View File

@ -11,6 +11,7 @@ pub mod error;
mod help;
pub mod logic;
pub mod matrix;
pub mod migrator;
pub mod models;
mod parser;
pub mod state;

17
src/migrate_cli.rs Normal file
View File

@ -0,0 +1,17 @@
use std::env;
pub mod migrator;
#[tokio::main]
async fn main() -> Result<(), migrator::MigrationError> {
let args: Vec<String> = env::args().collect();
let db_path: &str = match &args[..] {
[_, path] => path.as_ref(),
[_, _, ..] => panic!("Expected exactly 0 or 1 argument"),
_ => "dicebot.sqlite",
};
println!("Using database: {}", db_path);
migrator::migrate(db_path).await
}

33
src/migrator.rs Normal file
View File

@ -0,0 +1,33 @@
use log::info;
use refinery::config::{Config, ConfigDbType};
use sqlx::sqlite::SqliteConnectOptions;
use sqlx::ConnectOptions;
use std::str::FromStr;
use thiserror::Error;
pub mod migrations;
#[derive(Error, Debug)]
pub enum MigrationError {
#[error("sqlite connection error: {0}")]
SqlxError(#[from] sqlx::Error),
#[error("refinery migration error: {0}")]
RefineryError(#[from] refinery::Error),
}
/// Run database migrations against the sqlite database.
pub async fn migrate(db_path: &str) -> Result<(), MigrationError> {
//Create database if missing.
let conn = SqliteConnectOptions::from_str(&format!("sqlite://{}", db_path))?
.create_if_missing(true)
.connect()
.await?;
drop(conn);
let mut conn = Config::new(ConfigDbType::Sqlite).set_db_path(db_path);
info!("Running migrations");
migrations::runner().run(&mut conn)?;
Ok(())
}

View File

@ -0,0 +1,18 @@
use barrel::backend::Sqlite;
use barrel::{types, Migration};
use log::info;
pub fn migration() -> String {
let mut m = Migration::new();
info!("Applying migration: {}", file!());
m.create_table("user_variables", |t| {
t.add_column("id", types::primary());
t.add_column("room_id", types::text());
t.add_column("user_id", types::text());
t.add_column("key", types::text());
t.add_column("value", types::integer());
});
m.make::<Sqlite>()
}

View File

@ -0,0 +1,31 @@
use barrel::backend::Sqlite;
use barrel::{types, Migration};
use log::info;
pub fn migration() -> String {
let mut m = Migration::new();
info!("Applying migration: {}", file!());
//Table for basic room information: room ID, room name
m.create_table("room_info", move |t| {
t.add_column("id", types::primary());
t.add_column("room_id", types::text());
t.add_column("room_name", types::text());
});
//Table of users in rooms.
m.create_table("room_users", move |t| {
t.add_column("id", types::primary());
t.add_column("room_id", types::text());
t.add_column("username", types::text());
});
//Table of room ID, event ID, event timestamp
m.create_table("room_events", move |t| {
t.add_column("id", types::primary());
t.add_column("room_id", types::text());
t.add_column("event_id", types::text());
t.add_column("event_timestamp", types::integer());
});
m.make::<Sqlite>()
}

View File

@ -0,0 +1,2 @@
use refinery::include_migration_mods;
include_migration_mods!("src/migrator/migrations");