use warnings;
use base 'DBIx::Class';
-use mro 'c3';
use DBIx::Class::Carp;
-use Try::Tiny;
-use Scalar::Util qw/weaken blessed/;
+use Scalar::Util qw( weaken blessed refaddr );
use DBIx::Class::_Util qw(
- refcount quote_sub scope_guard
- is_exception dbic_internal_try
+ refdesc refcount quote_sub scope_guard
+ is_exception dbic_internal_try dbic_internal_catch
fail_on_internal_call emit_loud_diag
);
use Devel::GlobalDestruction;
__PACKAGE__->mk_classaccessor('class_mappings' => {});
__PACKAGE__->mk_classaccessor('source_registrations' => {});
+__PACKAGE__->mk_group_accessors( component_class => 'schema_sanity_checker' );
+__PACKAGE__->schema_sanity_checker(
+ 'DBIx::Class::Schema::SanityChecker'
+);
+
=head1 NAME
DBIx::Class::Schema - composable schemas
my $rs_class = ref ($_[0]) || $_[0];
return dbic_internal_try {
- $rs_class->result_source_instance
- } catch {
+ $rs_class->result_source
+ } dbic_internal_catch {
$me->throw_exception (
"Attempt to load_namespaces() class $rs_class failed - are you sure this is a real Result Class?: $_"
);
my @to_register;
{
- no warnings qw/redefine/;
- local *Class::C3::reinitialize = sub { } if DBIx::Class::_ENV_::OLD_MRO;
- use warnings qw/redefine/;
-
# ensure classes are loaded and attached in inheritance order
for my $result_class (values %$results_by_source_name) {
$class->ensure_class_loaded($result_class);
.'with no corresponding Result class';
}
- Class::C3->reinitialize if DBIx::Class::_ENV_::OLD_MRO;
-
$class->register_class(@$_) for (@to_register);
return;
my @to_register;
{
- no warnings qw/redefine/;
- local *Class::C3::reinitialize = sub { } if DBIx::Class::_ENV_::OLD_MRO;
- use warnings qw/redefine/;
-
foreach my $prefix (keys %comps_for) {
foreach my $comp (@{$comps_for{$prefix}||[]}) {
my $comp_class = "${prefix}::${comp}";
}
}
}
- Class::C3->reinitialize if DBIx::Class::_ENV_::OLD_MRO;
foreach my $to (@to_register) {
$class->register_class(@$to);
use base qw/DBIx::Class::Schema/;
__PACKAGE__->default_resultset_attributes( { software_limit => 1 } );
+=head2 schema_sanity_checker
+
+=over 4
+
+=item Arguments: L<perform_schema_sanity_checks()|DBIx::Class::Schema::SanityChecker/perform_schema_sanity_checks> provider
+
+=item Return Value: L<perform_schema_sanity_checks()|DBIx::Class::Schema::SanityChecker/perform_schema_sanity_checks> provider
+
+=item Default value: L<DBIx::Class::Schema::SanityChecker>
+
+=back
+
+On every call to L</connection> if the value of this attribute evaluates to
+true, DBIC will invoke
+C<< L<$schema_sanity_checker|/schema_sanity_checker>->L<perform_schema_sanity_checks|DBIx::Class::Schema::SanityChecker/perform_schema_sanity_checks>($schema) >>
+before returning. The return value of this invocation is ignored.
+
+B<YOU ARE STRONGLY URGED> to
+L<learn more about the reason|DBIx::Class::Schema::SanityChecker/WHY> this
+feature was introduced. Blindly disabling the checker on existing projects
+B<may result in data corruption> after upgrade to C<< DBIC >= v0.082900 >>.
+
+Example:
+
+ package My::Schema;
+ use base qw/DBIx::Class::Schema/;
+ __PACKAGE__->schema_sanity_checker('My::Schema::SanityChecker');
+
+ # or to disable all checks:
+ __PACKAGE__->schema_sanity_checker('');
+
+Note: setting the value to C<undef> B<will not> have the desired effect,
+due to an implementation detail of L<Class::Accessor::Grouped> inherited
+accessors. In order to disable any and all checks you must set this
+attribute to an empty string as shown in the second example above.
+
=head2 exception_action
=over 4
=cut
-sub connect {
+sub connect :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
shift->clone->connection(@_);
}
||
$self->throw_exception( "Can't find source for ${source_name}" )
;
+
+ # DO NOT REMOVE:
+ # We need to prevent alterations of pre-existing $@ due to where this call
+ # sits in the overall stack ( *unless* of course there is an actual error
+ # to report ). set_mro does alter $@ (and yes - it *can* throw an exception)
+ # We do not use local because set_mro *can* throw an actual exception
+ # We do not use a try/catch either, as on one hand it would slow things
+ # down for no reason (we would always rethrow), but also because adding *any*
+ # try/catch block below will segfault various threading tests on older perls
+ # ( which in itself is a FIXME but ENOTIMETODIG )
+ my $old_dollarat = $@;
+
+ no strict 'refs';
+ mro::set_mro($_, 'c3') for
+ grep
+ {
+ # some pseudo-sources do not have a result/resultset yet
+ defined $_
+ and
+ (
+ (
+ ${"${_}::__INITIAL_MRO_UPON_DBIC_LOAD__"}
+ ||= mro::get_mro($_)
+ )
+ ne
+ 'c3'
+ )
+ }
+ map
+ { length ref $_ ? ref $_ : $_ }
+ ( $rsrc, $rsrc->result_class, $rsrc->resultset_class )
+ ;
+
+ # DO NOT REMOVE - see comment above
+ $@ = $old_dollarat;
+
+ $rsrc;
}
=head2 class
=cut
-sub populate {
+sub populate :DBIC_method_is_indirect_sugar {
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
my ($self, $name, $data) = @_;
data B<in-place> on C<$self>. You should probably be calling
L</connect> to get a properly L<cloned|/clone> Schema object instead.
+If the accessor L</schema_sanity_checker> returns a true value C<$checker>,
+the following call will take place before return:
+C<< L<$checker|/schema_sanity_checker>->L<perform_schema_sanity_checks(C<$self>)|DBIx::Class::Schema::SanityChecker/perform_schema_sanity_checks> >>
+
=head3 Overloading
Overload C<connection> to change the behaviour of C<connect>.
=cut
+my $default_off_stderr_blurb_emitted;
sub connection {
my ($self, @info) = @_;
return $self if !@info && $self->storage;
dbic_internal_try {
$self->ensure_class_loaded ($storage_class);
}
- catch {
+ dbic_internal_catch {
$self->throw_exception(
"Unable to load storage class ${storage_class}: $_"
);
my $storage = $storage_class->new( $self => $args||{} );
$storage->connect_info(\@info);
$self->storage($storage);
- return $self;
+
+ if( my $checker = $self->schema_sanity_checker ) {
+ $checker->perform_schema_sanity_checks($self);
+ }
+
+ $self;
}
sub _normalize_storage_type {
#$schema->class_mappings({});
{
- no warnings qw/redefine/;
- local *Class::C3::reinitialize = sub { } if DBIx::Class::_ENV_::OLD_MRO;
- use warnings qw/redefine/;
-
foreach my $source_name ($self->sources) {
my $orig_source = $self->source($source_name);
my $target_class = "${target}::${source_name}";
$self->inject_base($target_class, $orig_source->result_class, ($base || ()) );
- # register_source examines result_class, and then returns us a clone
- my $new_source = $schema->register_source($source_name, bless
- { %$orig_source, result_class => $target_class },
- ref $orig_source,
+ $schema->register_source(
+ $source_name,
+ $orig_source->clone(
+ result_class => $target_class
+ ),
);
-
- if ($target_class->can('result_source_instance')) {
- # give the class a schema-less source copy
- $target_class->result_source_instance( bless
- { %$new_source, schema => ref $new_source->{schema} || $new_source->{schema} },
- ref $new_source,
- );
- }
}
# Legacy stuff, not inserting INDIRECT assertions
for qw(class source resultset);
}
- Class::C3->reinitialize() if DBIx::Class::_ENV_::OLD_MRO;
+ # needed to cover the newly installed stuff via quote_sub above
+ Class::C3->reinitialize if DBIx::Class::_ENV_::OLD_MRO;
+
+ # Give each composed class yet another *schema-less* source copy
+ # this is used for the freeze/thaw cycle
+ #
+ # This is not covered by any tests directly, but is indirectly exercised
+ # in t/cdbi/sweet/08pager by re-setting the schema on an existing object
+ # FIXME - there is likely a much cheaper way to take care of this
+ for my $source_name ($self->sources) {
+
+ my $target_class = "${target}::${source_name}";
+
+ $target_class->result_source_instance(
+ $self->source($source_name)->clone(
+ result_class => $target_class,
+ schema => ( ref $schema || $schema ),
+ )
+ );
+ }
return $schema;
}
$self->class_mappings({ %{$from->class_mappings} });
$self->source_registrations({ %{$from->source_registrations} });
- foreach my $source_name ($from->sources) {
- 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
- # but overwrite the source_registrations entry with the new source
- $self->register_extra_source($source_name => $new);
- }
+ # we use extra here as we want to leave the class_mappings as they are
+ # but overwrite the source_registrations entry with the new source
+ $self->register_extra_source( $_ => $from->source($_) )
+ for $from->sources;
if ($from->storage) {
$self->storage($from->storage);
1;
}
- catch {
+ dbic_internal_catch {
# We call this to get the necessary warnings emitted and disregard the RV
# as it's definitely an exception if we got as far as this catch{} block
is_exception(
Registers a class which isa DBIx::Class::ResultSourceProxy. Equivalent to
calling:
- $schema->register_source($source_name, $component_class->result_source_instance);
+ $schema->register_source($source_name, $component_class->result_source);
=cut
sub register_class {
my ($self, $source_name, $to_register) = @_;
- $self->register_source($source_name => $to_register->result_source_instance);
+ $self->register_source($source_name => $to_register->result_source);
}
=head2 register_source
sub _register_source {
my ($self, $source_name, $supplied_rsrc, $params) = @_;
- my $derived_rsrc = $supplied_rsrc->new({
- %$supplied_rsrc,
+ my $derived_rsrc = $supplied_rsrc->clone({
source_name => $source_name,
});
$derived_rsrc->schema($self);
weaken $derived_rsrc->{schema}
- if length ref($self);
+ if length( my $schema_class = ref($self) );
my %reg = %{$self->source_registrations};
$reg{$source_name} = $derived_rsrc;
$map{$result_class} = $source_name;
$self->class_mappings(\%map);
+
+
+ my $schema_class_level_rsrc;
+ if (
+ # we are called on a schema instance, not on the class
+ length $schema_class
+
+ and
+
+ # the schema class also has a registration with the same name
+ $schema_class_level_rsrc = dbic_internal_try { $schema_class->source($source_name) }
+
+ and
+
+ # what we are registering on the schema instance *IS* derived
+ # from the class-level (top) rsrc...
+ ( grep { $_ == $derived_rsrc } $result_class_level_rsrc->__derived_instances )
+
+ and
+
+ # ... while the schema-class-level has stale-markers
+ keys %{ $schema_class_level_rsrc->{__metadata_divergencies} || {} }
+ ) {
+ my $msg =
+ "The ResultSource instance you just registered on '$self' as "
+ . "'$source_name' seems to have no relation to $schema_class->"
+ . "source('$source_name') which in turn is marked stale (likely due "
+ . "to recent $result_class->... direct class calls). This is almost "
+ . "always a mistake: perhaps you forgot a cycle of "
+ . "$schema_class->unregister_source( '$source_name' ) / "
+ . "$schema_class->register_class( '$source_name' => '$result_class' )"
+ ;
+
+ DBIx::Class::_ENV_::ASSERT_NO_ERRONEOUS_METAINSTANCE_USE
+ ? emit_loud_diag( msg => $msg, confess => 1 )
+ : carp_unique($msg)
+ ;
+ }
}
$derived_rsrc;
dbic_internal_try {
require DBIx::Class::ResultSetProxy;
}
- catch {
+ dbic_internal_catch {
$self->throw_exception
("No arguments to load_classes and couldn't load DBIx::Class::ResultSetProxy ($_)")
};
my $schema = $self->compose_namespace($target, 'DBIx::Class::ResultSetProxy');
quote_sub "${target}::schema", '$s', { '$s' => \$schema };
+ # needed to cover the newly installed stuff via quote_sub above
+ Class::C3->reinitialize if DBIx::Class::_ENV_::OLD_MRO;
+
$schema->connection(@info);
foreach my $source_name ($schema->sources) {
my $source = $schema->source($source_name);
my $class = $source->result_class;
#warn "$source_name $class $source ".$source->storage;
- $class->mk_classaccessor(result_source_instance => $source);
+
+ $class->mk_group_accessors( inherited => [ result_source_instance => '_result_source' ] );
+ # explicit set-call, avoid mro update lag
+ $class->set_inherited( result_source_instance => $source );
+
$class->mk_classaccessor(resultset_instance => $source->resultset);
$class->mk_classaccessor(class_resolver => $schema);
}