Rework database to store all datasets in one sqlite file

This commit is contained in:
Kiana Sheibani 2023-09-27 15:19:28 -04:00
parent 3130d82e95
commit 0e9fddec9e
Signed by: toki
GPG key ID: 6CB106C25E86A9F7
2 changed files with 107 additions and 71 deletions

View file

@ -4,37 +4,67 @@ use std::fs::{self, OpenOptions};
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
/// Return the path to a dataset. /// Return the default path to the datasets file.
pub fn dataset_path(config_dir: &Path, dataset: &str) -> io::Result<PathBuf> { fn default_datasets_path(config_dir: &Path) -> io::Result<PathBuf> {
// $config_dir/datasets/$dataset.sqlite
let mut path = config_dir.to_owned(); let mut path = config_dir.to_owned();
path.push("datasets"); path.push("ggelo");
// Create datasets path if it doesn't exist // Create datasets path if it doesn't exist
fs::create_dir_all(&path)?; fs::create_dir_all(&path)?;
path.push(dataset); path.push("ggelo.db");
path.set_extension("db");
// Create dataset file if it doesn't exist // Create datasets file if it doesn't exist
OpenOptions::new().write(true).create(true).open(&path)?; OpenOptions::new().write(true).create(true).open(&path)?;
Ok(path) Ok(path)
} }
pub fn open_dataset(dataset: &Path) -> sqlite::Result<Connection> { pub fn open_datasets(config_dir: &Path, path: Option<&Path>) -> sqlite::Result<Connection> {
let path = path.map_or_else(
|| default_datasets_path(config_dir).unwrap(),
|p| p.to_owned(),
);
let query = " let query = "
CREATE TABLE IF NOT EXISTS players ( CREATE TABLE IF NOT EXISTS datasets (
name TEXT UNIQUE NOT NULL
) STRICT;
";
let connection = sqlite::open(path)?;
connection.execute(query)?;
Ok(connection)
}
// TODO: Sanitize dataset names
pub fn list_datasets(connection: &Connection) -> sqlite::Result<Vec<String>> {
let query = "SELECT * FROM datasets";
connection
.prepare(query)?
.into_iter()
.map(|x| x.map(|r| r.read::<&str, _>("name").to_owned()))
.try_collect()
}
pub fn new_dataset(connection: &Connection, dataset: &str) -> sqlite::Result<()> {
let query = format!(
"
INSERT INTO datasets VALUES ('{0}');
CREATE TABLE IF NOT EXISTS \"dataset_{0}\" (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
name TEXT, name TEXT,
prefix TEXT, prefix TEXT,
elo REAL NOT NULL elo REAL NOT NULL
) STRICT; ) STRICT;
"; ",
dataset
);
let connection = sqlite::open(dataset)?; connection.execute(query)
connection.execute(query)?;
Ok(connection)
} }
// Score calculation // Score calculation
@ -67,33 +97,40 @@ fn adjust_ratings(ratings: Teams<&mut f64>, winner: usize) {
// Database Updating // Database Updating
pub fn add_players(connection: &Connection, teams: &Teams<PlayerData>) -> sqlite::Result<()> { pub fn add_players(
let query = "INSERT OR IGNORE INTO players VALUES (?, ?, ?, 1500)"; connection: &Connection,
dataset: &str,
teams: &Teams<PlayerData>,
) -> sqlite::Result<()> {
let query = format!(
"INSERT OR IGNORE INTO \"dataset_{}\" VALUES (?, ?, ?, 1500)",
dataset
);
teams.iter().try_for_each(|team| { teams.iter().try_for_each(|team| {
team.iter().try_for_each(|PlayerData { id, name, prefix }| { team.iter().try_for_each(|PlayerData { id, name, prefix }| {
let mut statement = connection.prepare(query)?; let mut statement = connection.prepare(&query)?;
statement.bind((1, id.0 as i64))?; statement.bind((1, id.0 as i64))?;
statement.bind((2, name.as_ref().map(|x| &x[..])))?; statement.bind((2, name.as_ref().map(|x| &x[..])))?;
statement.bind((3, prefix.as_ref().map(|x| &x[..])))?; statement.bind((3, prefix.as_ref().map(|x| &x[..])))?;
statement.into_iter().try_for_each(|x| x.map(|_| ()))?; statement.into_iter().try_for_each(|x| x.map(|_| ()))
Ok(())
}) })
}) })
} }
pub fn get_ratings( pub fn get_ratings(
connection: &Connection, connection: &Connection,
dataset: &str,
teams: &Teams<PlayerData>, teams: &Teams<PlayerData>,
) -> sqlite::Result<Teams<(PlayerId, f64)>> { ) -> sqlite::Result<Teams<(PlayerId, f64)>> {
let query = "SELECT id, elo FROM players WHERE id = ?"; let query = format!("SELECT id, elo FROM \"dataset_{}\" WHERE id = ?", dataset);
teams teams
.iter() .iter()
.map(|team| { .map(|team| {
team.iter() team.iter()
.map(|data| { .map(|data| {
let mut statement = connection.prepare(query)?; let mut statement = connection.prepare(&query)?;
statement.bind((1, data.id.0 as i64))?; statement.bind((1, data.id.0 as i64))?;
statement.next()?; statement.next()?;
Ok((data.id, statement.read::<f64, _>("elo")?)) Ok((data.id, statement.read::<f64, _>("elo")?))
@ -103,29 +140,39 @@ pub fn get_ratings(
.try_collect() .try_collect()
} }
pub fn update_ratings(connection: &Connection, elos: Teams<(PlayerId, f64)>) -> sqlite::Result<()> { pub fn update_ratings(
let query = "UPDATE players SET elo = :elo WHERE id = :id"; connection: &Connection,
dataset: &str,
elos: Teams<(PlayerId, f64)>,
) -> sqlite::Result<()> {
let query = format!(
"UPDATE \"dataset_{}\" SET elo = :elo WHERE id = :id",
dataset
);
elos.into_iter().try_for_each(|team| { elos.into_iter().try_for_each(|team| {
team.into_iter().try_for_each(|(id, elo)| { team.into_iter().try_for_each(|(id, elo)| {
let mut statement = connection.prepare(query)?; let mut statement = connection.prepare(&query)?;
statement.bind((":elo", elo))?; statement.bind((":elo", elo))?;
statement.bind((":id", id.0 as i64))?; statement.bind((":id", id.0 as i64))?;
statement.into_iter().try_for_each(|x| x.map(|_| ()))?; statement.into_iter().try_for_each(|x| x.map(|_| ()))
Ok(())
}) })
}) })
} }
pub fn update_from_set(connection: &Connection, results: SetData) -> sqlite::Result<()> { pub fn update_from_set(
connection: &Connection,
dataset: &str,
results: SetData,
) -> sqlite::Result<()> {
let players_data = results.teams; let players_data = results.teams;
add_players(connection, &players_data)?; add_players(connection, dataset, &players_data)?;
let mut elos = get_ratings(connection, &players_data)?; let mut elos = get_ratings(connection, dataset, &players_data)?;
adjust_ratings( adjust_ratings(
elos.iter_mut() elos.iter_mut()
.map(|v| v.iter_mut().map(|x| &mut x.1).collect()) .map(|v| v.iter_mut().map(|x| &mut x.1).collect())
.collect(), .collect(),
results.winner, results.winner,
); );
update_ratings(connection, elos) update_ratings(connection, dataset, elos)
} }

View file

@ -2,6 +2,7 @@
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use std::io::{self, Write}; use std::io::{self, Write};
use std::path::{Path, PathBuf};
mod queries; mod queries;
use queries::*; use queries::*;
@ -17,12 +18,9 @@ use datasets::*;
#[command(author = "Kiana Sheibani <kiana.a.sheibani@gmail.com>")] #[command(author = "Kiana Sheibani <kiana.a.sheibani@gmail.com>")]
#[command(version = "0.1.0")] #[command(version = "0.1.0")]
#[command(about = "Elo rating calculator for start.gg tournaments", long_about = None)] #[command(about = "Elo rating calculator for start.gg tournaments", long_about = None)]
#[command(propagate_version = true)]
struct Cli { struct Cli {
#[command(subcommand)] #[command(subcommand)]
subcommand: Subcommands, subcommand: Subcommands,
#[arg(long)]
auth_token: Option<String>,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
@ -36,53 +34,44 @@ enum Subcommands {
#[derive(Subcommand)] #[derive(Subcommand)]
enum DatasetSC { enum DatasetSC {
List, List,
New { name: Option<String> },
} }
fn main() { fn main() {
let cli = Cli::parse(); let cli = Cli::parse();
let mut config_dir = dirs::config_dir().unwrap();
config_dir.push("ggelo");
let auth_token = get_auth_key(&config_dir).unwrap();
let app_state = AppState {
config_dir,
auth_token,
};
match cli.subcommand { match cli.subcommand {
Subcommands::Dataset { Subcommands::Dataset {
subcommand: DatasetSC::List, subcommand: DatasetSC::List,
} => dataset_list(app_state), } => dataset_list(),
Subcommands::Dataset {
subcommand: DatasetSC::New { name },
} => dataset_new(name),
} }
// let config = AppState {
// config_dir,
// auth_token,
// };
// let path = dataset_path(&config_dir, "test").unwrap();
// let connection = open_dataset(&path).unwrap();
// let set_data = SetData {
// teams: vec![
// vec![PlayerData {
// id: PlayerId(1),
// name: Some("player1".to_owned()),
// prefix: None,
// }],
// vec![PlayerData {
// id: PlayerId(2),
// name: Some("player2".to_owned()),
// prefix: None,
// }],
// ],
// winner: 0,
// };
// update_from_set(&connection, set_data.clone()).unwrap();
// println!("{:?}", get_ratings(&connection, &set_data.teams).unwrap());
} }
fn dataset_list(state: AppState) {} fn dataset_list() {
let config_dir = dirs::config_dir().unwrap();
let connection = open_datasets(&config_dir, None).unwrap();
let datasets = list_datasets(&connection).unwrap();
println!("{:?}", datasets);
}
fn dataset_new(name: Option<String>) {
let config_dir = dirs::config_dir().unwrap();
let name = name.unwrap_or_else(|| {
let mut line = String::new();
print!("Name of new dataset: ");
io::stdout().flush().expect("Could not access stdout");
io::stdin()
.read_line(&mut line)
.expect("Could not read from stdin");
line.trim().to_owned()
});
let connection = open_datasets(&config_dir, None).unwrap();
new_dataset(&connection, &name).unwrap();
}