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::path::{Path, PathBuf};
/// Return the path to a dataset.
pub fn dataset_path(config_dir: &Path, dataset: &str) -> io::Result<PathBuf> {
// $config_dir/datasets/$dataset.sqlite
/// Return the default path to the datasets file.
fn default_datasets_path(config_dir: &Path) -> io::Result<PathBuf> {
let mut path = config_dir.to_owned();
path.push("datasets");
path.push("ggelo");
// Create datasets path if it doesn't exist
fs::create_dir_all(&path)?;
path.push(dataset);
path.set_extension("db");
path.push("ggelo.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)?;
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 = "
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,
name TEXT,
prefix TEXT,
elo REAL NOT NULL
) STRICT;
";
",
dataset
);
let connection = sqlite::open(dataset)?;
connection.execute(query)?;
Ok(connection)
connection.execute(query)
}
// Score calculation
@ -67,33 +97,40 @@ fn adjust_ratings(ratings: Teams<&mut f64>, winner: usize) {
// Database Updating
pub fn add_players(connection: &Connection, teams: &Teams<PlayerData>) -> sqlite::Result<()> {
let query = "INSERT OR IGNORE INTO players VALUES (?, ?, ?, 1500)";
pub fn add_players(
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| {
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((2, name.as_ref().map(|x| &x[..])))?;
statement.bind((3, prefix.as_ref().map(|x| &x[..])))?;
statement.into_iter().try_for_each(|x| x.map(|_| ()))?;
Ok(())
statement.into_iter().try_for_each(|x| x.map(|_| ()))
})
})
}
pub fn get_ratings(
connection: &Connection,
dataset: &str,
teams: &Teams<PlayerData>,
) -> 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
.iter()
.map(|team| {
team.iter()
.map(|data| {
let mut statement = connection.prepare(query)?;
let mut statement = connection.prepare(&query)?;
statement.bind((1, data.id.0 as i64))?;
statement.next()?;
Ok((data.id, statement.read::<f64, _>("elo")?))
@ -103,29 +140,39 @@ pub fn get_ratings(
.try_collect()
}
pub fn update_ratings(connection: &Connection, elos: Teams<(PlayerId, f64)>) -> sqlite::Result<()> {
let query = "UPDATE players SET elo = :elo WHERE id = :id";
pub fn update_ratings(
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| {
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((":id", id.0 as i64))?;
statement.into_iter().try_for_each(|x| x.map(|_| ()))?;
Ok(())
statement.into_iter().try_for_each(|x| x.map(|_| ()))
})
})
}
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;
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(
elos.iter_mut()
.map(|v| v.iter_mut().map(|x| &mut x.1).collect())
.collect(),
results.winner,
);
update_ratings(connection, elos)
update_ratings(connection, dataset, elos)
}

View file

@ -2,6 +2,7 @@
use clap::{Parser, Subcommand};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
mod queries;
use queries::*;
@ -17,12 +18,9 @@ use datasets::*;
#[command(author = "Kiana Sheibani <kiana.a.sheibani@gmail.com>")]
#[command(version = "0.1.0")]
#[command(about = "Elo rating calculator for start.gg tournaments", long_about = None)]
#[command(propagate_version = true)]
struct Cli {
#[command(subcommand)]
subcommand: Subcommands,
#[arg(long)]
auth_token: Option<String>,
}
#[derive(Subcommand)]
@ -36,53 +34,44 @@ enum Subcommands {
#[derive(Subcommand)]
enum DatasetSC {
List,
New { name: Option<String> },
}
fn main() {
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 {
Subcommands::Dataset {
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();
}