Rework database to store all datasets in one sqlite file
This commit is contained in:
parent
3130d82e95
commit
0e9fddec9e
105
src/datasets.rs
105
src/datasets.rs
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
73
src/main.rs
73
src/main.rs
|
@ -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();
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue