implement ListBotMatches, allow querying matches by bot/opponent pair

This commit is contained in:
Ilion Beyst 2022-10-13 17:45:11 +02:00
parent ce07419bbc
commit 2278ecd258
4 changed files with 110 additions and 23 deletions

View file

@ -0,0 +1,86 @@
use super::matches::BotMatchOutcome;
use crate::schema::matches;
use chrono::NaiveDateTime;
use diesel::pg::Pg;
use diesel::query_builder::{AstPass, Query, QueryFragment, QueryId};
use diesel::sql_types::*;
use diesel::{PgConnection, QueryResult, RunQueryDsl};
pub struct ListBotMatches {
pub bot_id: i32,
pub opponent_id: Option<i32>,
pub outcome: Option<BotMatchOutcome>,
pub before: Option<NaiveDateTime>,
pub after: Option<NaiveDateTime>,
pub amount: i64,
}
impl QueryFragment<Pg> for ListBotMatches {
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> {
out.unsafe_to_cache_prepared();
out.push_sql("SELECT matches.* FROM matches");
out.push_sql(" JOIN (");
out.push_sql(concat!(
"SELECT match_id, player_id, bot_version_id, bot_id ",
"FROM match_players ",
"JOIN bot_versions ON match_players.bot_version_id = bot_versions.id ",
"WHERE bot_id = "
));
out.push_bind_param::<Integer, _>(&self.bot_id)?;
out.push_sql(") main_player ON matches.id = main_player.match_id");
if let Some(opponent_id) = self.opponent_id.as_ref() {
out.push_sql(" JOIN (");
out.push_sql(concat!(
"SELECT match_id, player_id, bot_version_id, bot_id ",
"FROM match_players ",
"JOIN bot_versions ON match_players.bot_version_id = bot_versions.id ",
"WHERE bot_id = "
));
out.push_bind_param::<Integer, _>(opponent_id)?;
out.push_sql(") other_player ON matches.id = other_player.match_id");
}
out.push_sql(" WHERE matches.state = 'finished' AND matches.is_public = true");
if let Some(outcome) = self.outcome.as_ref() {
match outcome {
BotMatchOutcome::Win => {
out.push_sql(" AND matches.winner = main_player.player_id");
}
BotMatchOutcome::Loss => {
out.push_sql(" AND matches.winner <> main_player.player_id");
}
BotMatchOutcome::Tie => {
out.push_sql(" AND matches.winner IS NULL");
}
}
}
if let Some(before) = self.before.as_ref() {
out.push_sql(" AND matches.created_at < ");
out.push_bind_param::<Timestamp, _>(before)?;
out.push_sql(" ORDER BY matches.created_at DESC");
} else if let Some(after) = self.after.as_ref() {
out.push_sql(" AND matches.created_at > ");
out.push_bind_param::<Timestamp, _>(after)?;
out.push_sql(" ORDER BY matches.created_at ASC");
}
out.push_sql(" LIMIT ");
out.push_bind_param::<BigInt, _>(&self.amount)?;
Ok(())
}
}
impl Query for ListBotMatches {
type SqlType = matches::SqlType;
}
impl QueryId for ListBotMatches {
type QueryId = ();
const HAS_STATIC_QUERY_ID: bool = false;
}
impl RunQueryDsl<PgConnection> for ListBotMatches {}

View file

@ -14,6 +14,7 @@ use crate::schema::{bot_versions, bots, maps, match_players, matches};
use super::bots::{Bot, BotVersion}; use super::bots::{Bot, BotVersion};
use super::maps::Map; use super::maps::Map;
use super::match_queries::ListBotMatches;
#[derive(Insertable)] #[derive(Insertable)]
#[diesel(table_name = matches)] #[diesel(table_name = matches)]
@ -173,35 +174,23 @@ pub fn list_public_matches(
pub fn list_bot_matches( pub fn list_bot_matches(
bot_id: i32, bot_id: i32,
opponent_id: Option<i32>,
outcome: Option<BotMatchOutcome>, outcome: Option<BotMatchOutcome>,
amount: i64, amount: i64,
before: Option<NaiveDateTime>, before: Option<NaiveDateTime>,
after: Option<NaiveDateTime>, after: Option<NaiveDateTime>,
conn: &mut PgConnection, conn: &mut PgConnection,
) -> QueryResult<Vec<FullMatchData>> { ) -> QueryResult<Vec<FullMatchData>> {
let mut query = finished_public_matches_query(before, after) let lbm = ListBotMatches {
.inner_join(match_players::table) bot_id,
.inner_join( opponent_id,
bot_versions::table.on(match_players::bot_version_id.eq(bot_versions::id.nullable())), outcome,
) before,
.filter(bot_versions::bot_id.eq(bot_id)) after,
.select(matches::all_columns); amount,
};
if let Some(outcome) = outcome { let matches = lbm.get_results::<MatchBase>(conn)?;
query = match outcome {
BotMatchOutcome::Win => {
query.filter(matches::winner.eq(match_players::player_id.nullable()))
}
BotMatchOutcome::Loss => {
query.filter(matches::winner.ne(match_players::player_id.nullable()))
}
BotMatchOutcome::Tie => query.filter(matches::winner.is_null()),
};
}
query = query.limit(amount);
let matches = query.get_results::<MatchBase>(conn)?;
fetch_full_match_data(matches, conn) fetch_full_match_data(matches, conn)
} }

View file

@ -1,5 +1,6 @@
pub mod bots; pub mod bots;
pub mod maps; pub mod maps;
pub mod match_queries;
pub mod matches; pub mod matches;
pub mod ratings; pub mod ratings;
pub mod sessions; pub mod sessions;

View file

@ -42,6 +42,7 @@ pub struct ListRecentMatchesParams {
after: Option<NaiveDateTime>, after: Option<NaiveDateTime>,
bot: Option<String>, bot: Option<String>,
opponent: Option<String>,
outcome: Option<BotMatchOutcome>, outcome: Option<BotMatchOutcome>,
} }
@ -70,8 +71,18 @@ pub async fn list_recent_matches(
Some(bot_name) => { Some(bot_name) => {
let bot = db::bots::find_bot_by_name(&bot_name, &mut conn) let bot = db::bots::find_bot_by_name(&bot_name, &mut conn)
.map_err(|_| StatusCode::BAD_REQUEST)?; .map_err(|_| StatusCode::BAD_REQUEST)?;
let opponent_id = if let Some(opponent_name) = params.opponent {
let opponent = db::bots::find_bot_by_name(&opponent_name, &mut conn)
.map_err(|_| StatusCode::BAD_REQUEST)?;
Some(opponent.id)
} else {
None
};
matches::list_bot_matches( matches::list_bot_matches(
bot.id, bot.id,
opponent_id,
params.outcome, params.outcome,
count, count,
params.before, params.before,
@ -82,7 +93,7 @@ pub async fn list_recent_matches(
None => matches::list_public_matches(count, params.before, params.after, &mut conn), None => matches::list_public_matches(count, params.before, params.after, &mut conn),
}; };
let mut matches = matches_result.map_err(|_| StatusCode::BAD_REQUEST)?; let mut matches = matches_result.expect("failed to get matches"); //.map_err(|_| StatusCode::BAD_REQUEST)?;
let mut has_next = false; let mut has_next = false;
if matches.len() > requested_count { if matches.len() > requested_count {