From 71872fd01b28bae8565917b97dcef05113fd0c9e Mon Sep 17 00:00:00 2001 From: Mateu Hunter Date: Sun, 5 Apr 2026 13:23:37 -0600 Subject: [PATCH] perf: use set-based SQL for portable update_points --- lib/Bracket/Model/DBIC.pm | 74 +++++++++++++++++++++------------------ t/model_update_points.t | 22 ++++++++++++ 2 files changed, 62 insertions(+), 34 deletions(-) diff --git a/lib/Bracket/Model/DBIC.pm b/lib/Bracket/Model/DBIC.pm index b78c0c0..807c60a 100644 --- a/lib/Bracket/Model/DBIC.pm +++ b/lib/Bracket/Model/DBIC.pm @@ -249,45 +249,51 @@ sub _update_points_portable { $times{round_out} = $current_time - $previous_time; $previous_time = $current_time; - my %points_for; - foreach my $pick ($schema->resultset('Pick')->search({}, { prefetch => [qw/game pick/] })->all) { - my $game_id = $pick->get_column('game'); - my $winner_team_id = $perfect_winner_for_game{$game_id}; - next if !defined $winner_team_id || $winner_team_id != $pick->get_column('pick'); - - my $game_row = $pick->game; - my $team_row = $pick->pick; - my $points_for_pick = $game_row->get_column('round') * - (5 + $game_row->get_column('lower_seed') * $team_row->get_column('seed')); - $points_for{$pick->get_column('player')}{ $team_row->get_column('region') } += $points_for_pick; - } - - $schema->txn_do(sub { - foreach my $player ($schema->resultset('Player')->search({})->all) { - my $player_id = $player->get_column('id'); - foreach my $region_id (1 .. 4) { - my $points = $points_for{$player_id}{$region_id} || 0; - $schema->resultset('RegionScore')->update_or_create({ - player => $player_id, - region => $region_id, - points => $points, - }); - } - } + my $storage = $schema->storage; + $storage->dbh_do(sub { + my ($storage_self, $dbh) = @_; + $dbh->do('delete from region_score'); + $dbh->do(q{ + insert into region_score (player, region, points) + select + player.id as player, + region.id as region, + coalesce(player_region_points.points, 0) as points + from player + cross join region + left join ( + select + player_picks.player as player, + team.region as region, + sum(game.round * (5 + game.lower_seed * team.seed)) as points + from pick player_picks + join pick perfect_picks + on perfect_picks.player = 1 + and perfect_picks.game = player_picks.game + and perfect_picks.pick = player_picks.pick + join game + on game.id = player_picks.game + join team + on team.id = player_picks.pick + group by player_picks.player, team.region + ) as player_region_points + on player_region_points.player = player.id + and player_region_points.region = region.id + }); }); $current_time = time(); $times{update_region_score} = $current_time - $previous_time; $previous_time = $current_time; - $schema->txn_do(sub { - foreach my $player ($schema->resultset('Player')->search({})->all) { - my $player_id = $player->get_column('id'); - my $total_points = 0; - foreach my $region_id (1 .. 4) { - $total_points += $points_for{$player_id}{$region_id} || 0; - } - $player->update({ points => $total_points }); - } + $storage->dbh_do(sub { + my ($storage_self, $dbh) = @_; + $dbh->do(q{ + update player + set points = coalesce( + (select sum(points) from region_score where region_score.player = player.id), + 0 + ) + }); }); $current_time = time(); $times{update_player_points} = $current_time - $previous_time; diff --git a/t/model_update_points.t b/t/model_update_points.t index 9a6c8fe..3d4c56d 100644 --- a/t/model_update_points.t +++ b/t/model_update_points.t @@ -15,6 +15,12 @@ my $player = $schema->resultset('Player')->create({ first_name => 'Portable', last_name => 'Points', }); +my $zero_player = $schema->resultset('Player')->create({ + email => 'portable-zero@example.com', + password => 'secret', + first_name => 'Zero', + last_name => 'Points', +}); sub set_pick { my ($player_id, $game_id, $team_id) = @_; @@ -42,6 +48,9 @@ set_pick(1, 9, 2); # winner from game 1 advances set_pick($player->id, 1, 2); set_pick($player->id, 2, 4); set_pick($player->id, 9, 2); +set_pick($zero_player->id, 1, 1); +set_pick($zero_player->id, 2, 4); +set_pick($zero_player->id, 9, 3); my $stats = Bracket::Model::DBIC::_update_points_for_schema($schema); like($stats, qr/total time:/, 'update_points reports execution stats'); @@ -74,4 +83,17 @@ is($all_region_scores, 4, 'portable path maintains all region score rows per pla my $player_row = $schema->resultset('Player')->find($player->id); is($player_row->get_column('points'), 47, 'player total points updated from region scores'); +my $zero_region_scores = $schema->resultset('RegionScore')->search({ player => $zero_player->id }); +is($zero_region_scores->count, 4, 'player with no winning picks still has all region score rows'); +is( + $zero_region_scores->get_column('points')->sum, + 0, + 'player with no winning picks has zero total region points' +); +is( + $schema->resultset('Player')->find($zero_player->id)->get_column('points'), + 0, + 'player total points zeroed when they have no correct picks' +); + done_testing();