diff options
Diffstat (limited to 'planetwars-server/src/db/matches.rs')
-rw-r--r-- | planetwars-server/src/db/matches.rs | 152 |
1 files changed, 129 insertions, 23 deletions
diff --git a/planetwars-server/src/db/matches.rs b/planetwars-server/src/db/matches.rs index 061e2ea..2041296 100644 --- a/planetwars-server/src/db/matches.rs +++ b/planetwars-server/src/db/matches.rs @@ -1,20 +1,27 @@ pub use crate::db_types::MatchState; use chrono::NaiveDateTime; use diesel::associations::BelongsTo; +use diesel::pg::Pg; +use diesel::query_builder::BoxedSelectStatement; +use diesel::query_source::{AppearsInFromClause, Once}; use diesel::{ BelongingToDsl, ExpressionMethods, JoinOnDsl, NullableExpressionMethods, QueryDsl, RunQueryDsl, }; use diesel::{Connection, GroupedBy, PgConnection, QueryResult}; +use std::collections::{HashMap, HashSet}; -use crate::schema::{bot_versions, bots, match_players, matches}; +use crate::schema::{bot_versions, bots, maps, match_players, matches}; use super::bots::{Bot, BotVersion}; +use super::maps::Map; #[derive(Insertable)] #[table_name = "matches"] pub struct NewMatch<'a> { pub state: MatchState, pub log_path: &'a str, + pub is_public: bool, + pub map_id: Option<i32>, } #[derive(Insertable)] @@ -36,6 +43,8 @@ pub struct MatchBase { pub log_path: String, pub created_at: NaiveDateTime, pub winner: Option<i32>, + pub is_public: bool, + pub map_id: Option<i32>, } #[derive(Queryable, Identifiable, Associations, Clone)] @@ -87,42 +96,133 @@ pub struct MatchData { pub match_players: Vec<MatchPlayer>, } +/// Add player information to MatchBase instances +fn fetch_full_match_data( + matches: Vec<MatchBase>, + conn: &PgConnection, +) -> QueryResult<Vec<FullMatchData>> { + let map_ids: HashSet<i32> = matches.iter().filter_map(|m| m.map_id).collect(); + + let maps_by_id: HashMap<i32, Map> = maps::table + .filter(maps::id.eq_any(map_ids)) + .load::<Map>(conn)? + .into_iter() + .map(|m| (m.id, m)) + .collect(); + + let match_players = MatchPlayer::belonging_to(&matches) + .left_join( + bot_versions::table.on(match_players::bot_version_id.eq(bot_versions::id.nullable())), + ) + .left_join(bots::table.on(bot_versions::bot_id.eq(bots::id.nullable()))) + .order_by(( + match_players::match_id.asc(), + match_players::player_id.asc(), + )) + .load::<FullMatchPlayerData>(conn)? + .grouped_by(&matches); + + let res = matches + .into_iter() + .zip(match_players.into_iter()) + .map(|(base, players)| FullMatchData { + match_players: players.into_iter().collect(), + map: base + .map_id + .and_then(|map_id| maps_by_id.get(&map_id).cloned()), + base, + }) + .collect(); + + Ok(res) +} + +// TODO: this method should disappear pub fn list_matches(amount: i64, conn: &PgConnection) -> QueryResult<Vec<FullMatchData>> { conn.transaction(|| { let matches = matches::table + .filter(matches::state.eq(MatchState::Finished)) .order_by(matches::created_at.desc()) .limit(amount) .get_results::<MatchBase>(conn)?; - let match_players = MatchPlayer::belonging_to(&matches) - .left_join( - bot_versions::table - .on(match_players::bot_version_id.eq(bot_versions::id.nullable())), - ) - .left_join(bots::table.on(bot_versions::bot_id.eq(bots::id.nullable()))) - .order_by(( - match_players::match_id.asc(), - match_players::player_id.asc(), - )) - .load::<FullMatchPlayerData>(conn)? - .grouped_by(&matches); - - let res = matches - .into_iter() - .zip(match_players.into_iter()) - .map(|(base, players)| FullMatchData { - base, - match_players: players.into_iter().collect(), - }) - .collect(); + fetch_full_match_data(matches, conn) + }) +} - Ok(res) +pub fn list_public_matches( + amount: i64, + before: Option<NaiveDateTime>, + after: Option<NaiveDateTime>, + conn: &PgConnection, +) -> QueryResult<Vec<FullMatchData>> { + conn.transaction(|| { + // TODO: how can this common logic be abstracted? + let query = matches::table + .filter(matches::state.eq(MatchState::Finished)) + .filter(matches::is_public.eq(true)) + .into_boxed(); + + let matches = + select_matches_page(query, amount, before, after).get_results::<MatchBase>(conn)?; + fetch_full_match_data(matches, conn) }) } +pub fn list_bot_matches( + bot_id: i32, + amount: i64, + before: Option<NaiveDateTime>, + after: Option<NaiveDateTime>, + conn: &PgConnection, +) -> QueryResult<Vec<FullMatchData>> { + let query = matches::table + .filter(matches::state.eq(MatchState::Finished)) + .filter(matches::is_public.eq(true)) + .order_by(matches::created_at.desc()) + .inner_join(match_players::table) + .inner_join( + bot_versions::table.on(match_players::bot_version_id.eq(bot_versions::id.nullable())), + ) + .filter(bot_versions::bot_id.eq(bot_id)) + .select(matches::all_columns) + .into_boxed(); + + let matches = + select_matches_page(query, amount, before, after).get_results::<MatchBase>(conn)?; + fetch_full_match_data(matches, conn) +} + +fn select_matches_page<QS>( + query: BoxedSelectStatement<'static, matches::SqlType, QS, Pg>, + amount: i64, + before: Option<NaiveDateTime>, + after: Option<NaiveDateTime>, +) -> BoxedSelectStatement<'static, matches::SqlType, QS, Pg> +where + QS: AppearsInFromClause<matches::table, Count = Once>, +{ + // TODO: this is not nice. Replace this with proper cursor logic. + match (before, after) { + (None, None) => query.order_by(matches::created_at.desc()), + (Some(before), None) => query + .filter(matches::created_at.lt(before)) + .order_by(matches::created_at.desc()), + (None, Some(after)) => query + .filter(matches::created_at.gt(after)) + .order_by(matches::created_at.asc()), + (Some(before), Some(after)) => query + .filter(matches::created_at.lt(before)) + .filter(matches::created_at.gt(after)) + .order_by(matches::created_at.desc()), + } + .limit(amount) +} + // TODO: maybe unify this with matchdata? pub struct FullMatchData { pub base: MatchBase, + pub map: Option<Map>, pub match_players: Vec<FullMatchPlayerData>, } @@ -151,6 +251,11 @@ pub fn find_match(id: i32, conn: &PgConnection) -> QueryResult<FullMatchData> { conn.transaction(|| { let match_base = matches::table.find(id).get_result::<MatchBase>(conn)?; + let map = match match_base.map_id { + None => None, + Some(map_id) => Some(super::maps::find_map(map_id, conn)?), + }; + let match_players = MatchPlayer::belonging_to(&match_base) .left_join( bot_versions::table @@ -163,6 +268,7 @@ pub fn find_match(id: i32, conn: &PgConnection) -> QueryResult<FullMatchData> { let res = FullMatchData { base: match_base, match_players, + map, }; Ok(res) |