The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!perl

use Test::More;
use Test::Deep;
use Test::Exception;
use Data::Dumper;
use autodie;
plan 'tests' => 12;

use Pg::Explain;

my $plan_source = q{                                                                  QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=17.05..17.17 rows=45 width=133) (actual time=1.633..1.633 rows=0 loops=1)
   Sort Key: n.nspname, c.relname
   Sort Method: quicksort  Memory: 25kB
   ->  Hash Join  (cost=1.14..15.82 rows=45 width=133) (actual time=1.599..1.599 rows=0 loops=1)
         Hash Cond: (c.relnamespace = n.oid)
         ->  Seq Scan on pg_class c  (cost=0.00..13.27 rows=45 width=73) (actual time=0.036..1.419 rows=93 loops=1)
               Filter: (pg_table_is_visible(oid) AND (relkind = ANY ('{r,v,S,f,""}'::"char"[])) AND relname = 'timestamp with time zone')
         ->  Hash  (cost=1.10..1.10 rows=3 width=68) (actual time=0.120..0.120 rows=2 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 1kB
               ->  Seq Scan on pg_namespace n  (cost=0.00..1.10 rows=3 width=68) (actual time=0.109..0.118 rows=2 loops=1)
                     Filter: ((nspname <> 'pg_catalog'::name) AND (nspname <> 'information_schema'::name) AND (nspname !~ '^pg_toast'::text))
 Total runtime: 1.777 ms
};

my $explain = Pg::Explain->new( 'source' => $plan_source );
isa_ok( $explain,           'Pg::Explain' );
isa_ok( $explain->top_node, 'Pg::Explain::Node' );

lives_ok(
    sub {
        $explain->anonymize();
    },
    'Anonymization works',
);

my $textual = $explain->as_text();

ok( $textual =~ /::"char"\[\]/, 'anonymize() preserves type casting' );
ok( $textual =~ /::name\b/, 'anonymize() preserves type casting' );
ok( $textual =~ /::text\b/, 'anonymize() preserves type casting' );
ok( $textual !~ /timestamp with time zone/, 'anonymize() hides string literals' );
ok( $textual !~ /nspname/, 'anonymize() hides column names' );
ok( $textual !~ /pg_class/, 'anonymize() hides relation names' );

my $reparsed = Pg::Explain->new( 'source' => $textual );
isa_ok( $reparsed,           'Pg::Explain' );
isa_ok( $reparsed->top_node, 'Pg::Explain::Node' );

my $expected = $explain->top_node->get_struct();
my $got      = $reparsed->top_node->get_struct();

just_numbers( $expected );
just_numbers( $got );

cmp_deeply( $got, $expected, 'Plan numbers are the same' );

exit;

sub just_numbers {
    my $what = shift;
    return unless 'HASH' eq ref $what;
    delete $what->{ 'extra_info' };
    delete $what->{ 'scan_on' };
    delete $what->{ 'type' };

    for my $key ( grep { 'ARRAY' eq ref $what->{ $_ } } keys %{ $what } ) {
        for my $item ( @{ $what->{ $key } } ) {
            just_numbers( $item );
        }
    }
    return;
}