#!/usr/bin/perl # For info: https://dev.yorhel.nl/doc/sqlobject # Date: 2019-08-13 # License: MIT use v5.12; use warnings; use DBIx::Simple; use SQL::Interp 'sql'; use Data::Dumper 'Dumper'; my $db = DBIx::Simple->connect('dbi:Pg:dbname=test', undef, undef, { RaiseError => 1 }); $db->query($_) for (split /;/, <<_); DROP SCHEMA IF EXISTS sqlobject_test CASCADE; CREATE SCHEMA sqlobject_test; SET search_path TO sqlobject_test; CREATE TABLE games ( game_id integer PRIMARY KEY, title text ); CREATE TABLE releases ( release_id integer PRIMARY KEY, game_id integer REFERENCES games, release_date date ); CREATE TABLE releases_platforms ( release_id integer REFERENCES releases, platform text ); INSERT INTO games VALUES (7, 'Tsukihime'); INSERT INTO releases VALUES (339, 7, '2000-12-29'); INSERT INTO releases VALUES (341, 7, '2006-12-28'); INSERT INTO releases_platforms VALUES (339, 'Linux'); INSERT INTO releases_platforms VALUES (339, 'Windows'); INSERT INTO releases_platforms VALUES (341, 'Windows') _ { my @games = $db->query('SELECT game_id, title FROM games')->hashes; for my $game (@games) { $game->{releases} = [ $db->query( 'SELECT release_id, release_date FROM releases WHERE game_id = ?', $game->{game_id} )->hashes ]; } print Dumper \@games; } { # Our abstraction to fetch game entries. my sub fetch_games { my $game_ids = shift; $db->query("SELECT * FROM games WHERE game_id IN($game_ids)")->hashes; # We could be doing some transformations here. # Sorting, pagination, fetching extra information, etc. } # We can use that function to fetch games that have already been released. my $ids = "SELECT game_id FROM releases WHERE release_date <= CURRENT_DATE"; my @released_games = fetch_games($ids); print Dumper \@released_games; } { my sub fetch_games { my $game_ids = shift; # Note the use of `iquery` instead of `query`, # this is a DBIx::Simple wrapper around `sql_interp()`. $db->iquery("SELECT * FROM games WHERE game_id IN(", $game_ids, ")")->hashes; } my $latest_date = '2019-01-20'; # Our user input # This is our "SQL + bind parameters" in a single variable. my $ids = sql("SELECT game_id FROM releases WHERE release_date <=", \$latest_date); # ...which can be passed to fetch_games(): my @games_with_release_before_latest_date = fetch_games($ids); print Dumper \@games_with_release_before_latest_date; } { my @games = $db->query('SELECT game_id, title FROM games')->hashes; # List of game_ids, which we can use for our IN() clause. my @game_ids = map $_->{game_id}, @games; # Fetch all the releases related to @game_ids. my @releases = $db->iquery( 'SELECT game_id, release_id, release_date FROM releases WHERE game_id IN', \@game_ids )->hashes; # Create a 'game_id' => [releases] lookup table for quick access my %releases; for my $release (@releases) { # Add this release to %releases and remove the 'game_id' column, # which was only needed for this merging step. push @{$releases{ delete $release->{game_id} }}, $release; } # Now merge the release information back into @games for my $game (@games) { $game->{releases} = $releases{ $game->{game_id} } || []; } print Dumper \@games; } sub enrich { my($field_name, $merge_field, $sql, @objects) = @_; my %ids = map +($_->{$merge_field}, []), @objects; return if !keys %ids; my @result = $db->iquery( $sql->([keys %ids]) )->hashes; push @{$ids{ delete $_->{$merge_field} }}, $_ for (@result); $_->{ $field_name } = $ids{ $_->{$merge_field} } for (@objects); } sub enrich_flatten { # This is the same as the original enrich() my($field_name, $merge_field, $sql, @objects) = @_; my %ids = map +($_->{$merge_field}, []), @objects; return if !keys %ids; my @result = $db->iquery( $sql->([keys %ids]) )->hashes; # This is the actual merge strategy, which we'll modify slightly: push @{$ids{ delete $_->{$merge_field} }}, values %$_ for (@result); $_->{ $field_name } = $ids{ $_->{$merge_field} } for (@objects); } { my @games = $db->query('SELECT game_id, title FROM games')->hashes; enrich 'releases', 'game_id', sub { sql 'SELECT game_id, release_id, release_date FROM releases WHERE game_id IN', $_[0] }, @games; # More nesting enrich 'platforms', 'release_id', sub { sql 'SELECT release_id, platform FROM releases_platforms WHERE release_id IN', $_[0] }, map @{$_->{releases}}, @games; # Flattened platforms enrich_flatten 'platforms_flat', 'release_id', sub { sql 'SELECT release_id, platform FROM releases_platforms WHERE release_id IN', $_[0] }, map @{$_->{releases}}, @games; print Dumper \@games; } { my @games = $db->query('SELECT game_id, title FROM games')->hashes; # This is the per-game platforms list. enrich_flatten 'platforms', 'game_id', sub { sql 'SELECT DISTINCT game_id, platform FROM releases JOIN releases_platforms USING (release_id) WHERE game_id IN', $_[0] }, @games; my $latest_date = '2004-01-20'; # Our user input # This is the releases list. enrich 'releases', 'game_id', sub { sql 'SELECT game_id, release_id, release_date, (SELECT COUNT(*) FROM releases_platforms rp WHERE rp.release_id = r.release_id) AS platform_count FROM releases r WHERE game_id IN', $_[0], ' AND release_date <=', \$latest_date, ' ORDER BY release_date' }, @games; print Dumper \@games; }