From efd8969fed27b6abc7e18737af448649b7e75237 Mon Sep 17 00:00:00 2001 From: Jan Henning Thorsen Date: Fri, 23 Aug 2013 14:37:58 +0200 Subject: [PATCH 1/7] Add support for load_namespaces(lazy_load => 1, ...) All tests successful. Files=94, Tests=20844, 25 wallclock secs Result: PASS All database related tests was skipped when running this test suite. --- lib/DBIx/Class/Schema.pm | 48 +++++++++++++++++++++++++++++++++--- t/39load_namespaces_lazy.t | 34 +++++++++++++++++++++++++ t/lib/DBICNSTest/Result/R.pm | 13 ++++++++++ 3 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 t/39load_namespaces_lazy.t create mode 100644 t/lib/DBICNSTest/Result/R.pm diff --git a/lib/DBIx/Class/Schema.pm b/lib/DBIx/Class/Schema.pm index 4c3cce50e..766778b69 100644 --- a/lib/DBIx/Class/Schema.pm +++ b/lib/DBIx/Class/Schema.pm @@ -102,6 +102,11 @@ All of the namespace and classname options are by default relative to the schema classname. To specify a fully-qualified name, prefix it with a literal C<+>. For example, C<+Other::NameSpace::Result>. +Experimental: It is also possible to load the result source classes when they +are requested by L. Giving C as an argument to +L will enable this. This will decrease start up time if you +have large schemas. + =head3 Warnings You will be warned if ResultSet classes are discovered for which there @@ -137,6 +142,12 @@ L to some other class, you will be warned like this: resultset_namespace => '+Another::Place::RSets', ); + # Postpone loading of result sources + My::Schema->load_namespaces( + lazy_load => 1, + # ... + ); + To search multiple namespaces for either Result or ResultSet classes, use an arrayref of namespaces for that option. In the case that the same result (or resultset) class exists in multiple namespaces, later @@ -207,6 +218,7 @@ sub load_namespaces { my $resultset_namespace = delete $args{resultset_namespace} || 'ResultSet'; my $default_resultset_class = delete $args{default_resultset_class}; + my $lazy_load = delete $args{lazy_load}; $default_resultset_class = $class->_expand_relative_name($default_resultset_class) if $default_resultset_class; @@ -228,7 +240,14 @@ sub load_namespaces { my $results_by_source_name = $class->_map_namespaces($result_namespace); my $resultsets_by_source_name = $class->_map_namespaces($resultset_namespace); + if($lazy_load) { + # abusing the attribute to store $moniker => [@classnames] information + $class->class_mappings->{$_} = [ $results{$_}, $resultsets{$_} || $default_resultset_class ] for keys %results; + return; + } + my @to_register; + { no warnings qw/redefine/; local *Class::C3::reinitialize = sub { } if DBIx::Class::_ENV_::OLD_MRO; @@ -591,10 +610,31 @@ sub source { my $sreg = $self->source_registrations; return $sreg->{$source_name} if exists $sreg->{$source_name}; - # if we got here, they probably passed a full class name + # if we got here, they probably passed a full class name or something to lazy load my $mapped = $self->class_mappings->{$source_name}; - $self->throw_exception("Can't find source for ${source_name}") - unless $mapped && exists $sreg->{$mapped}; + + # if we got here, they probably passed a full class name + if(!$mapped) { + my $last; + for(%{ $self->class_mappings }) { + next unless ref $_ and $_->[0] eq $source_name; + $mapped = $_; + last; + } + } + if(ref $mapped eq 'ARRAY') { + my $source_class = $mapped->[0]; + $self->ensure_class_loaded($source_class); + $source_class->resultset_class($mapped->[1]) if $mapped->[1]; + $self->register_class($source_name, $source_class); + $mapped = $source_name; + $sreg = $self->source_registrations; # jhthorsen: No idea why data is copied all over the place instead of just changing the ref... + } + + if(!$mapped or !exists $sreg->{$mapped}) { + $self->throw_exception("Can't find source for ${source_name}"); + } + return $sreg->{$mapped}; } @@ -1014,7 +1054,7 @@ sub clone { }; bless $clone, (ref $self || $self); - $clone->$_(undef) for qw/class_mappings source_registrations storage/; + $clone->$_(undef) for qw/class_mappings source_registrations storage /; $clone->_copy_state_from($self); diff --git a/t/39load_namespaces_lazy.t b/t/39load_namespaces_lazy.t new file mode 100644 index 000000000..168ba333d --- /dev/null +++ b/t/39load_namespaces_lazy.t @@ -0,0 +1,34 @@ +use strict; +use warnings; +use Test::More; + +use lib qw(t/lib); +use DBICTest; # do not remove even though it is not used + +plan tests => 9; + +my $warnings; +eval { + local $SIG{__WARN__} = sub { $warnings .= shift }; + package DBICNSTest; + use base qw/DBIx::Class::Schema/; + __PACKAGE__->load_namespaces(lazy_load => 1, default_resultset_class => 'RSBase'); +}; +ok !$@ or diag $@; +ok !$warnings, 'no warnings'; + +is int DBICNSTest->sources, 0, 'zero sources loaded'; + +my $source_b = DBICNSTest->source('R'); +isa_ok($source_b, 'DBIx::Class::ResultSource::Table'); +my $rset_b = DBICNSTest->resultset('R'); +isa_ok($rset_b, 'DBICNSTest::RSBase'); +ok ref $source_b->related_source('a'), 'managed to load related'; + +my $source_a = DBICNSTest->source('A'); +isa_ok($source_a, 'DBIx::Class::ResultSource::Table'); +my $rset_a = DBICNSTest->resultset('A'); +isa_ok($rset_a, 'DBICNSTest::ResultSet::A'); + + +is int DBICNSTest->sources, 3, 'two sources loaded'; diff --git a/t/lib/DBICNSTest/Result/R.pm b/t/lib/DBICNSTest/Result/R.pm new file mode 100644 index 000000000..dd98019f1 --- /dev/null +++ b/t/lib/DBICNSTest/Result/R.pm @@ -0,0 +1,13 @@ +package DBICNSTest::Result::R; + +use warnings; +use strict; + +use base qw/DBIx::Class::Core/; +__PACKAGE__->table('r'); +__PACKAGE__->add_columns('r'); +__PACKAGE__->belongs_to( + a => 'DBICNSTest::Result::A', + { 'foreign.a' => 'this.r' }, +); +1; From 1dae24c571aa6acde90b06c44b0b157656598b86 Mon Sep 17 00:00:00 2001 From: Jan Henning Thorsen Date: Thu, 5 Sep 2013 09:41:43 +0200 Subject: [PATCH 2/7] lazy_load: sources() returns available source names --- lib/DBIx/Class/Schema.pm | 48 +++++++++++++++++--------------------- t/39load_namespaces_lazy.t | 27 ++++++++++++--------- 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/lib/DBIx/Class/Schema.pm b/lib/DBIx/Class/Schema.pm index 766778b69..ffe298f6d 100644 --- a/lib/DBIx/Class/Schema.pm +++ b/lib/DBIx/Class/Schema.pm @@ -241,8 +241,12 @@ sub load_namespaces { my $resultsets_by_source_name = $class->_map_namespaces($resultset_namespace); if($lazy_load) { - # abusing the attribute to store $moniker => [@classnames] information - $class->class_mappings->{$_} = [ $results{$_}, $resultsets{$_} || $default_resultset_class ] for keys %results; + for(keys %results) { + # $source_class => [$source_name, $resultset_class] + $class->class_mappings->{$results{$_}} = [ $_, $resultsets{$_} || $default_resultset_class ]; + # $source_name => [$source_class, $resultset_class] + $class->source_registrations->{$_} = [ $results{$_}, $resultsets{$_} || $default_resultset_class ]; + } return; } @@ -606,36 +610,28 @@ sub source { unless @_; my $source_name = shift; - my $sreg = $self->source_registrations; - return $sreg->{$source_name} if exists $sreg->{$source_name}; - # if we got here, they probably passed a full class name or something to lazy load - my $mapped = $self->class_mappings->{$source_name}; + if(exists $sreg->{$source_name}) { + my $source = $sreg->{$source_name}; + return $self->_lazy_source($source_name, @$source) if ref $source eq 'ARRAY'; + return $source; + } # if we got here, they probably passed a full class name - if(!$mapped) { - my $last; - for(%{ $self->class_mappings }) { - next unless ref $_ and $_->[0] eq $source_name; - $mapped = $_; - last; - } - } - if(ref $mapped eq 'ARRAY') { - my $source_class = $mapped->[0]; - $self->ensure_class_loaded($source_class); - $source_class->resultset_class($mapped->[1]) if $mapped->[1]; - $self->register_class($source_name, $source_class); - $mapped = $source_name; - $sreg = $self->source_registrations; # jhthorsen: No idea why data is copied all over the place instead of just changing the ref... - } + my $mapped = $self->class_mappings->{$source_name}; - if(!$mapped or !exists $sreg->{$mapped}) { - $self->throw_exception("Can't find source for ${source_name}"); - } + $self->throw_exception("Can't find source for ${source_name}") unless $mapped; + return $self->_lazy_source($mapped->[0], $source_name, $mapped->[1]) if ref $mapped eq 'ARRAY'; + return $mapped; +} + +sub _lazy_source { + my($self, $source_name, $source_class, $resultset_class) = @_; - return $sreg->{$mapped}; + $self->ensure_class_loaded($source_class); + $source_class->resultset_class($resultset_class) if $resultset_class; + $self->source_registrations->{$source_name} = $self->register_class($source_name, $source_class); } =head2 class diff --git a/t/39load_namespaces_lazy.t b/t/39load_namespaces_lazy.t index 168ba333d..bd2daba26 100644 --- a/t/39load_namespaces_lazy.t +++ b/t/39load_namespaces_lazy.t @@ -5,7 +5,7 @@ use Test::More; use lib qw(t/lib); use DBICTest; # do not remove even though it is not used -plan tests => 9; +plan tests => 12; my $warnings; eval { @@ -14,21 +14,26 @@ eval { use base qw/DBIx::Class::Schema/; __PACKAGE__->load_namespaces(lazy_load => 1, default_resultset_class => 'RSBase'); }; -ok !$@ or diag $@; +ok !$@, 'namespace is loaded' or diag $@; ok !$warnings, 'no warnings'; -is int DBICNSTest->sources, 0, 'zero sources loaded'; +is_deeply( + [ sort DBICNSTest->sources ], + [ qw/ A B D R / ], + 'sources() available' +); -my $source_b = DBICNSTest->source('R'); -isa_ok($source_b, 'DBIx::Class::ResultSource::Table'); -my $rset_b = DBICNSTest->resultset('R'); -isa_ok($rset_b, 'DBICNSTest::RSBase'); -ok ref $source_b->related_source('a'), 'managed to load related'; +for(DBICNSTest->sources) { + is ref DBICNSTest->source_registrations->{$_}, 'ARRAY', "$_ is lazy"; +} + +my $source_r = DBICNSTest->source('R'); +isa_ok($source_r, 'DBIx::Class::ResultSource::Table'); +my $rset_r = DBICNSTest->resultset('R'); +isa_ok($rset_r, 'DBICNSTest::RSBase'); +ok ref $source_r->related_source('a'), 'managed to load related'; my $source_a = DBICNSTest->source('A'); isa_ok($source_a, 'DBIx::Class::ResultSource::Table'); my $rset_a = DBICNSTest->resultset('A'); isa_ok($rset_a, 'DBICNSTest::ResultSet::A'); - - -is int DBICNSTest->sources, 3, 'two sources loaded'; From ffd98fbcf0eb4c493bc0f11d55dacfac1973a993 Mon Sep 17 00:00:00 2001 From: Jan Henning Thorsen Date: Thu, 5 Sep 2013 10:46:12 +0200 Subject: [PATCH 3/7] Made fa96ac0 work wit the rest of the test suite All tests successful. Files=94, Tests=20848, 51 wallclock secs Result: PASS --- lib/DBIx/Class/Schema.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/DBIx/Class/Schema.pm b/lib/DBIx/Class/Schema.pm index ffe298f6d..d794e2696 100644 --- a/lib/DBIx/Class/Schema.pm +++ b/lib/DBIx/Class/Schema.pm @@ -623,7 +623,7 @@ sub source { $self->throw_exception("Can't find source for ${source_name}") unless $mapped; return $self->_lazy_source($mapped->[0], $source_name, $mapped->[1]) if ref $mapped eq 'ARRAY'; - return $mapped; + return $sreg->{$mapped}; } sub _lazy_source { From b7c2f1f2d5e74a1426048f9e12f8c622b7a2a2c6 Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Fri, 5 Sep 2014 18:27:15 +0100 Subject: [PATCH 4/7] Fix lazy load sources for latest releases --- lib/DBIx/Class/Schema.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/DBIx/Class/Schema.pm b/lib/DBIx/Class/Schema.pm index d794e2696..c549c0564 100644 --- a/lib/DBIx/Class/Schema.pm +++ b/lib/DBIx/Class/Schema.pm @@ -241,11 +241,11 @@ sub load_namespaces { my $resultsets_by_source_name = $class->_map_namespaces($resultset_namespace); if($lazy_load) { - for(keys %results) { + for(keys %$results_by_source_name) { # $source_class => [$source_name, $resultset_class] - $class->class_mappings->{$results{$_}} = [ $_, $resultsets{$_} || $default_resultset_class ]; + $class->class_mappings->{$results_by_source_name->{$_}} = [ $_, $resultsets_by_source_name->{$_} || $default_resultset_class ]; # $source_name => [$source_class, $resultset_class] - $class->source_registrations->{$_} = [ $results{$_}, $resultsets{$_} || $default_resultset_class ]; + $class->source_registrations->{$_} = [ $results_by_source_name->{$_}, $resultsets_by_source_name->{$_} || $default_resultset_class ]; } return; } From 8f7ebb093248d0b8393bff963c5aeb1c3dced2b2 Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Sat, 6 Sep 2014 17:14:49 +0000 Subject: [PATCH 5/7] Fix connect vivifying all sources anyway --- lib/DBIx/Class/Schema.pm | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/DBIx/Class/Schema.pm b/lib/DBIx/Class/Schema.pm index c549c0564..cb06a4123 100644 --- a/lib/DBIx/Class/Schema.pm +++ b/lib/DBIx/Class/Schema.pm @@ -12,6 +12,7 @@ use DBIx::Class::_Util 'refcount'; use Sub::Name 'subname'; use Devel::GlobalDestruction; use namespace::clean; +use Module::Loaded; __PACKAGE__->mk_classdata('class_mappings' => {}); __PACKAGE__->mk_classdata('source_registrations' => {}); @@ -1063,9 +1064,16 @@ sub _copy_state_from { my ($self, $from) = @_; $self->class_mappings({ %{$from->class_mappings} }); - $self->source_registrations({ %{$from->source_registrations} }); + + my $sregs = { %{$from->source_registrations} }; + $self->source_registrations($sregs); foreach my $source_name ($from->sources) { + + # Skip any source that isn't yet created + # This allows lazy to work past connect + next unless is_loaded($source_name); + my $source = $from->source($source_name); my $new = $source->new($source); # we use extra here as we want to leave the class_mappings as they are From 5ff4f94fef4da99a40a9a2f888253a33f51ea674 Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Sun, 7 Sep 2014 18:24:56 +0100 Subject: [PATCH 6/7] Fix Skipping of non-loaded sources. Checking the file simply won't work, but even if it would, this wasn't the module name. The source_registrations gives us a better handle on "has this been vivified or not" --- lib/DBIx/Class/Schema.pm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/DBIx/Class/Schema.pm b/lib/DBIx/Class/Schema.pm index cb06a4123..5a6d26ebd 100644 --- a/lib/DBIx/Class/Schema.pm +++ b/lib/DBIx/Class/Schema.pm @@ -1070,9 +1070,11 @@ sub _copy_state_from { foreach my $source_name ($from->sources) { - # Skip any source that isn't yet created - # This allows lazy to work past connect - next unless is_loaded($source_name); + # Skip any source which isn't actually an object yet + # The hatchlings for sources are just arrayrefs + unless (blessed $sregs->{ $source_name }){ + next; + } my $source = $from->source($source_name); my $new = $source->new($source); From ea168c0fc8eddb630a1822719273993f84e42095 Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Sun, 7 Sep 2014 18:32:16 +0100 Subject: [PATCH 7/7] Fix Module::Loaded leaked. Also noted that it's after namespace autoclean! --- lib/DBIx/Class/Schema.pm | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/DBIx/Class/Schema.pm b/lib/DBIx/Class/Schema.pm index 5a6d26ebd..ecd0fadff 100644 --- a/lib/DBIx/Class/Schema.pm +++ b/lib/DBIx/Class/Schema.pm @@ -12,7 +12,6 @@ use DBIx::Class::_Util 'refcount'; use Sub::Name 'subname'; use Devel::GlobalDestruction; use namespace::clean; -use Module::Loaded; __PACKAGE__->mk_classdata('class_mappings' => {}); __PACKAGE__->mk_classdata('source_registrations' => {});