package DBIx::Class::TopoSort;
#vi:sw=2

use 5.008_004;

use strict;
use warnings FATAL => 'all';

our $VERSION = '0.050000';

use Graph;

sub toposort_graph {
  my $self = shift;
  my ($schema, %opts) = ref($self) ? ($self, @_) : @_;

  my $g = Graph->new;

  my @source_names = $schema->sources;

  my %table_source = map { 
    $schema->source($_)->name => $_
  } @source_names;

  foreach my $name ( @source_names ) {
    my $source = $schema->source($name);
    $g->add_vertex($name);

    foreach my $rel_name ( $source->relationships ) {
      next if grep { $_ eq $rel_name } @{$opts{skip}{$name}};
      my $rel_info = $source->relationship_info($rel_name);

      if ( $rel_info->{attrs}{is_foreign_key_constraint} ) {
        $g->add_edge(
          $table_source{$schema->source($rel_info->{source})->name},
          $name,
        );
      }
    }
  }

  return $g;
}

sub toposort {
  my $self = shift;
  return $self->toposort_graph(@_)->toposort();
}

1;
__END__

=head1 NAME

DBIx::Class::TopoSort - The addition of topological sorting to DBIx::Class

=head1 SYNOPSIS

Without using TopoSort as a component:

  my @toposorted_sourcenames = DBIx::Class::TopoSort->toposort($schema);

  my @toposorted_sourcenames = DBIx::Class::TopoSort->toposort($schema, skip => {
          Artist => [qw/ first_album /],
  });


Alternately, as a component - within your schema class:

  __PACKAGE__->load_components('TopoSort');

Later:

  my $schema = Your::App::Schema->connect(...);
  my @toposorted_sourcenames = $schema->toposort();

If you have a cycle in your relationships

  my @toposorted_sourcenames = $schema->toposort(
      skip => {
          Artist => [qw/ first_album /],
      },
  );

=head1 DESCRIPTION

This adds the ability to return the list of sources (similar to
L<DBIx::Class::Schema/sources>) in topological-sorted order. This can be used
either as a component or as an independent method.

=head2 TOPOLOGICAL SORT

A topological sort of the tables returns the list of tables such that any table
with a foreign key relationship appears after any table it has a foreign key
relationship to.

=head1 METHODS

Both methods may be used either as class methods of TopoSort or as methods of
the schema (when TopoSort is loaded as a component). This class is not
instantiable.

=head2 toposort($schema?, %opts?)

This is sugar for:

  $self->toposort_graph(@_)->toposort();

Calling this method multiple times may return the list of source names in
different order. Each order will conform to the gurantee described in the
section on TOPOLOGICAL SORT.

This method will throw an error if there are any cycles in your tables. You will
need to specify the skip parameter (described below) to break those cycles.

=head2 toposort_graph($schema?, %opts?)

This returns a L<Graph> object with a vertex for every source and an edge for
every foreign key relationship.

It takes the following parameters.

=over 4

=item skip

This describes the list of relationships that should be ignored by the toposort
algorithm. This is generally used if you have cycles in your schema (though it
could possibly be useful in other ways, I guess). The value is a hashref. The
keys of this hashref are source names and the values are arrays of relationship
names.

  skip => {
      Artist => [ qw/ first_album / ],
  },

=back

=head1 SEE ALSO

L<Graph/toposort>

=head1 AUTHORS

=over 4

=item * Rob Kinyon <rob.kinyon@gmail.com>

=back

=head1 LICENSE

Copyright (c) 2013 Rob Kinyon. All Rights Reserved.
This is free software, you may use it and distribute it under the same terms
as Perl itself.

=cut
