From: Arthur Axel 'fREW' Schmidt Date: Sun, 13 May 2012 19:08:11 +0000 (-0500) Subject: nearly there with conversion of source_tree X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=ecfc76d50249e197fba7736c7007494f868f517a;p=dbsrgits%2FDBIx-Class.git nearly there with conversion of source_tree --- diff --git a/lib/DBIx/Class/Schema.pm b/lib/DBIx/Class/Schema.pm index 2550cb5..f7dc646 100644 --- a/lib/DBIx/Class/Schema.pm +++ b/lib/DBIx/Class/Schema.pm @@ -1536,6 +1536,150 @@ sub compose_connection { return $schema; } +sub source_tree { + my $self = shift; + my $args = shift; + + my %limit_sources = do { + my $l = $args->{limit_sources}; + my $ref = ref $l; + + $ref eq 'HASH' ? + %$l + : map { $_ => 1} @$l + }; + + my %table_monikers = + map { $_ => 1 } + grep { $self->source($_)->isa('DBIx::Class::ResultSource::Table') } + grep { !$limit_sources{$_} } + $self->sources; + + my %tables; + foreach my $moniker (sort keys %table_monikers) { + my $source = $self->source($moniker); + my $table_name = $source->name; + + # FIXME - this isn't the right way to do it, but sqlt does not + # support quoting properly to be signaled about this + $table_name = $$table_name if ref $table_name eq 'SCALAR'; + + # It's possible to have multiple DBIC sources using the same table + next if $tables{$table_name}; + + $tables{$table_name}{source} = $source; + + foreach my $rel (sort $source->relationships) { + my $rel_info = $source->relationship_info($rel); + + # FIXME - we can probably do better, at least check if it's a + # coderef that returns a plain hash w/ fk-fk + # Ignore any rel cond that isn't a straight hash + next unless ref $rel_info->{cond} eq 'HASH'; + + my $relsource = try { $source->related_source($rel) }; + next unless $relsource; + + # related sources might be excluded via a {sources} filter or might be views + next unless exists $table_monikers{$relsource->source_name}; + + my $rel_table = $relsource->name; + + # FIXME - this isn't the right way to do it, but sqlt does not + # support quoting properly to be signaled about this + $rel_table = $$rel_table if ref $rel_table eq 'SCALAR'; + + # Force the order of @cond to match the order of ->add_columns + my $idx; + my %other_columns_idx = map {'foreign.'.$_ => ++$idx } $relsource->columns; + my @cond = sort { $other_columns_idx{$a} cmp $other_columns_idx{$b} } keys(%{$rel_info->{cond}}); + + # Get the key information, mapping off the foreign/self markers + my @keys = map {$rel_info->{cond}->{$_} =~ /^\w+\.(\w+)$/} @cond; + + # determine if this relationship is a self.fk => foreign.pk (i.e. belongs_to) + my $fk_constraint; + + #first it can be specified explicitly + if ( exists $rel_info->{attrs}{is_foreign_key_constraint} ) { + $fk_constraint = $rel_info->{attrs}{is_foreign_key_constraint}; + } + # it can not be multi + elsif ( $rel_info->{attrs}{accessor} + && $rel_info->{attrs}{accessor} eq 'multi' ) { + $fk_constraint = 0; + } + # if indeed single, check if all self.columns are our primary keys. + # this is supposed to indicate a has_one/might_have... + # where's the introspection!!?? :) + else { + $fk_constraint = not $source->_compare_relationship_keys( + \@keys, [$source->primary_columns]); + } + + $tables{$table_name}{foreign_table_deps}{$rel_table}++ + if $fk_constraint && @keys + # calculate dependencies: do not consider deferrable constraints and + # self-references for dependency calculations + && !$rel_info->{attrs}{is_deferrable} + && $rel_table && $rel_table ne $table_name + + } + } + + return { + map { $_ => $self->_resolve_deps ($_, \%tables) } (keys %tables) + } +} + +sub _resolve_deps { + my ( $self, $question, $answers, $seen ) = @_; + my $ret = {}; + $seen ||= {}; + my @deps; + + # copy and bump all deps by one (so we can reconstruct the chain) + my %seen = map { $_ => $seen->{$_} + 1 } ( keys %$seen ); + if ( blessed($question) + && $question->isa('DBIx::Class::ResultSource::View') ) + { + $seen{ $question->result_class } = 1; + @deps = keys %{ $question->{deploy_depends_on} }; + } + else { + $seen{$question} = 1; + @deps = keys %{ $answers->{$question}{foreign_table_deps} }; + } + + for my $dep (@deps) { + if ( $seen->{$dep} ) { + return {}; + } + my $next_dep; + + if ( blessed($question) + && $question->isa('DBIx::Class::ResultSource::View') ) + { + no warnings 'uninitialized'; + my ($next_dep_source_name) = + grep { + $question->schema->source($_)->result_class eq $dep + && !( $question->schema->source($_) + ->isa('DBIx::Class::ResultSource::Table') ) + } @{ [ $question->schema->sources ] }; + return {} unless $next_dep_source_name; + $next_dep = $question->schema->source($next_dep_source_name); + } + else { + $next_dep = $dep; + } + my $subdeps = $self->_resolve_deps( $next_dep, $answers, \%seen ); + $ret->{$_} += $subdeps->{$_} for ( keys %$subdeps ); + ++$ret->{$dep}; + } + return $ret; +} + 1; =head1 AUTHORS diff --git a/t/schema/source-tree.t b/t/schema/source-tree.t new file mode 100644 index 0000000..5fd1bb8 --- /dev/null +++ b/t/schema/source-tree.t @@ -0,0 +1,323 @@ +use strict; +use warnings; +use Test::More; + +use lib qw(t/lib); +use DBICTest; + +my $schema = DBICTest->init_schema( no_connect => 1, no_deploy => 1 ); + +is_deeply($schema->source_tree, { + " ( SELECT a.*, cd.cdid AS cdid, cd.title AS title, cd.year AS year\n FROM artist a\n JOIN cd ON cd.artist = a.artistid\n WHERE cd.year = ?)\n" => {}, + artist => {}, + artist_undirected_map => { + artist => 1 + }, + artwork_to_artist => { + artist => 1, + cd => 1, + cd_artwork => 1, + genre => 1, + track => 1 + }, + bindtype_test => {}, + bookmark => { + link => 1 + }, + books => { + owners => 1 + }, + cd => { + genre => 1, + track => 1 + }, + cd_artwork => { + cd => 1, + genre => 1, + track => 1 + }, + cd_to_producer => { + cd => 1, + genre => 1, + producer => 1, + track => 1 + }, + collection => {}, + collection_object => { + collection => 1, + typed_object => 1 + }, + dummy => {}, + employee => { + encoded => 1 + }, + encoded => {}, + event => {}, + forceforeign => { + artist => 1 + }, + fourkeys => {}, + fourkeys_to_twokeys => { + artist => 1, + cd => 1, + fourkeys => 1, + genre => 1, + track => 1, + twokeys => 1 + }, + genre => {}, + images => { + cd => 1, + cd_artwork => 1, + genre => 1, + track => 1 + }, + liner_notes => { + cd => 1, + genre => 1, + track => 1 + }, + link => {}, + lyric_versions => { + cd => 1, + lyrics => 1, + track => 1 + }, + lyrics => { + cd => 1, + track => 1 + }, + money_test => {}, + noprimarykey => {}, + onekey => {}, + owners => {}, + producer => {}, + self_ref => {}, + self_ref_alias => { + self_ref => 1 + }, + sequence_test => {}, + serialized => {}, + tags => { + cd => 1, + genre => 1, + track => 1 + }, + timestamp_primary_key_test => {}, + track => { + cd => 1 + }, + treelike => {}, + twokeys => { + artist => 1, + cd => 1, + genre => 1, + track => 1 + }, + twokeytreelike => {}, + typed_object => {}, +}, 'got correct source tree'); + +is_deeply($schema->source_tree({ limit_sources => ['TwoKeys'] }), { + " ( SELECT a.*, cd.cdid AS cdid, cd.title AS title, cd.year AS year\n FROM artist a\n JOIN cd ON cd.artist = a.artistid\n WHERE cd.year = ?)\n" => {}, + artist => {}, + artist_undirected_map => { + artist => 1 + }, + artwork_to_artist => { + artist => 1, + cd => 1, + cd_artwork => 1, + genre => 1, + track => 1 + }, + bindtype_test => {}, + bookmark => { + link => 1 + }, + books => { + owners => 1 + }, + cd => { + genre => 1, + track => 1 + }, + cd_artwork => { + cd => 1, + genre => 1, + track => 1 + }, + cd_to_producer => { + cd => 1, + genre => 1, + producer => 1, + track => 1 + }, + collection => {}, + collection_object => { + collection => 1, + typed_object => 1 + }, + dummy => {}, + employee => { + encoded => 1 + }, + encoded => {}, + event => {}, + forceforeign => { + artist => 1 + }, + fourkeys => {}, + fourkeys_to_twokeys => { + fourkeys => 1 + }, + genre => {}, + images => { + cd => 1, + cd_artwork => 1, + genre => 1, + track => 1 + }, + liner_notes => { + cd => 1, + genre => 1, + track => 1 + }, + link => {}, + lyric_versions => { + cd => 1, + lyrics => 1, + track => 1 + }, + lyrics => { + cd => 1, + track => 1 + }, + money_test => {}, + noprimarykey => {}, + onekey => {}, + owners => {}, + producer => {}, + self_ref => {}, + self_ref_alias => { + self_ref => 1 + }, + sequence_test => {}, + serialized => {}, + tags => { + cd => 1, + genre => 1, + track => 1 + }, + timestamp_primary_key_test => {}, + track => { + cd => 1 + }, + treelike => {}, + twokeytreelike => {}, + typed_object => {} +}, 'got correct source tree with limit_sources => [ ... ]'); + +is_deeply($schema->source_tree({ limit_sources => { TwoKeys => 1 } }), { + " ( SELECT a.*, cd.cdid AS cdid, cd.title AS title, cd.year AS year\n FROM artist a\n JOIN cd ON cd.artist = a.artistid\n WHERE cd.year = ?)\n" => {}, + artist => {}, + artist_undirected_map => { + artist => 1 + }, + artwork_to_artist => { + artist => 1, + cd => 1, + cd_artwork => 1, + genre => 1, + track => 1 + }, + bindtype_test => {}, + bookmark => { + link => 1 + }, + books => { + owners => 1 + }, + cd => { + genre => 1, + track => 1 + }, + cd_artwork => { + cd => 1, + genre => 1, + track => 1 + }, + cd_to_producer => { + cd => 1, + genre => 1, + producer => 1, + track => 1 + }, + collection => {}, + collection_object => { + collection => 1, + typed_object => 1 + }, + dummy => {}, + employee => { + encoded => 1 + }, + encoded => {}, + event => {}, + forceforeign => { + artist => 1 + }, + fourkeys => {}, + fourkeys_to_twokeys => { + fourkeys => 1 + }, + genre => {}, + images => { + cd => 1, + cd_artwork => 1, + genre => 1, + track => 1 + }, + liner_notes => { + cd => 1, + genre => 1, + track => 1 + }, + link => {}, + lyric_versions => { + cd => 1, + lyrics => 1, + track => 1 + }, + lyrics => { + cd => 1, + track => 1 + }, + money_test => {}, + noprimarykey => {}, + onekey => {}, + owners => {}, + producer => {}, + self_ref => {}, + self_ref_alias => { + self_ref => 1 + }, + sequence_test => {}, + serialized => {}, + tags => { + cd => 1, + genre => 1, + track => 1 + }, + timestamp_primary_key_test => {}, + track => { + cd => 1 + }, + treelike => {}, + twokeytreelike => {}, + typed_object => {} +}, 'got correct source tree with limit_sources => { ... }'); + +# We probably also want a "collapsed" tree + +done_testing;