use Class::Unload;
require DBIx::Class;
- our $VERSION = '0.04999_10';
+ our $VERSION = '0.04999_11';
__PACKAGE__->mk_ro_accessors(qw/
schema
_tables
classes
monikers
+ dynamic
/);
+__PACKAGE__->mk_accessors(qw/
+ version_to_dump
+/);
+
=head1 NAME
DBIx::Class::Schema::Loader::Base - Base DBIx::Class::Schema::Loader Implementation.
Skip setting up relationships. The default is to attempt the loading
of relationships.
+=head2 naming
+
+Static schemas (ones dumped to disk) will, by default, use the new-style 0.05XXX
+relationship names and singularized Results, unless you're overwriting an
+existing dump made by a 0.04XXX version of L<DBIx::Class::Schema::Loader>, in
+which case the backward compatible RelBuilder will be activated, and
+singularization will be turned off.
+
+Specifying
+
+ naming => 'v5'
+
+will disable the backward-compatible RelBuilder and use
+the new-style relationship names along with singularized Results, even when
+overwriting a dump made with an earlier version.
+
+The option also takes a hashref:
+
+ naming => { relationships => 'v5', results => 'v4' }
+
+The values can be:
+
+=over 4
+
+=item current
+
+Latest default style, whatever that happens to be.
+
+=item v5
+
+Version 0.05XXX style.
+
+=item v4
+
+Version 0.04XXX style.
+
+=back
+
+Dynamic schemas will always default to the 0.04XXX relationship names and won't
+singularize Results for backward compatibility, to activate the new RelBuilder
+and singularization put this in your C<Schema.pm> file:
+
+ __PACKAGE__->naming('current');
+
+Or if you prefer to use 0.05XXX features but insure that nothing breaks in the
+next major version upgrade:
+
+ __PACKAGE__->naming('v5');
+
=head2 debug
If set to true, each constructive L<DBIx::Class> statement the loader
$self->{dump_directory} ||= $self->{temp_directory};
- $self->{relbuilder} = DBIx::Class::Schema::Loader::RelBuilder->new(
- $self->schema, $self->inflect_plural, $self->inflect_singular
- ) if !$self->{skip_relationships};
+ $self->version_to_dump($DBIx::Class::Schema::Loader::VERSION);
+
+ $self->_check_back_compat;
$self;
}
+sub _check_back_compat {
+ my ($self) = @_;
+
+# dynamic schemas will always be in 0.04006 mode
+ if ($self->dynamic) {
+ no strict 'refs';
+ my $class = ref $self || $self;
+ require DBIx::Class::Schema::Loader::Compat::v0_040;
+ unshift @{"${class}::ISA"},
+ 'DBIx::Class::Schema::Loader::Compat::v0_040';
+ Class::C3::reinitialize;
+# just in case, though no one is likely to dump a dynamic schema
+ $self->version_to_dump('0.04006');
+ return;
+ }
+
+# otherwise check if we need backcompat mode for a static schema
+ my $filename = $self->_get_dump_filename($self->schema_class);
+ return unless -e $filename;
+
+ open(my $fh, '<', $filename)
+ or croak "Cannot open '$filename' for reading: $!";
+
+ while (<$fh>) {
+ if (/^# Created by DBIx::Class::Schema::Loader v((\d+)\.(\d+))/) {
+ my $real_ver = $1;
+ my $ver = "v${2}_${3}";
+ while (1) {
+ my $compat_class = "DBIx::Class::Schema::Loader::Compat::${ver}";
+ if ($self->load_optional_class($compat_class)) {
+ no strict 'refs';
+ my $class = ref $self || $self;
+ unshift @{"${class}::ISA"}, $compat_class;
+ Class::C3::reinitialize;
+ $self->version_to_dump($real_ver);
+ last;
+ }
+ $ver =~ s/\d\z// or last;
+ }
+ last;
+ }
+ }
+ close $fh;
+}
+
sub _find_file_in_inc {
my ($self, $file) = @_;
return;
}
-sub _load_external {
+sub _class_path {
my ($self, $class) = @_;
my $class_path = $class;
$class_path =~ s{::}{/}g;
$class_path .= '.pm';
- my $real_inc_path = $self->_find_file_in_inc($class_path);
+ return $class_path;
+}
+
+sub _find_class_in_inc {
+ my ($self, $class) = @_;
+
+ return $self->_find_file_in_inc($self->_class_path($class));
+}
+
+sub _load_external {
+ my ($self, $class) = @_;
+
+ my $real_inc_path = $self->_find_class_in_inc($class);
return if !$real_inc_path;
warn qq/# Loaded external class definition for '$class'\n/
if $self->debug;
- croak 'Failed to locate actual external module file for '
- . "'$class'"
- if !$real_inc_path;
open(my $fh, '<', $real_inc_path)
or croak "Failed to open '$real_inc_path' for reading: $!";
$self->_ext_stmt($class,
);
close($fh)
or croak "Failed to close $real_inc_path: $!";
+
+# load the class too
+ {
+ # turn off redefined warnings
+ $SIG{__WARN__} = sub {};
+ do $real_inc_path;
+ }
+ die $@ if $@;
}
=head2 load
my ($self, $schema) = @_;
$self->{schema} = $schema;
- $self->{relbuilder}{schema} = $schema;
+ $self->_relbuilder->{schema} = $schema;
my @created;
my @current = $self->_tables_list;
return map { $self->monikers->{$_} } @$loaded;
}
+sub _relbuilder {
+ my ($self) = @_;
+
+ return if $self->{skip_relationships};
+
+ $self->{relbuilder} ||= DBIx::Class::Schema::Loader::RelBuilder->new(
+ $self->schema, $self->inflect_plural, $self->inflect_singular
+ );
+}
+
sub _load_tables {
my ($self, @tables) = @_;
# The relationship loader needs a working schema
$self->{quiet} = 1;
local $self->{dump_directory} = $self->{temp_directory};
- $self->_reload_classes(@tables);
+ $self->_reload_classes(\@tables);
$self->_load_relationships($_) for @tables;
$self->{quiet} = 0;
$self->_load_external($_)
for map { $self->classes->{$_} } @tables;
- $self->_reload_classes(@tables);
+ # Reload without unloading first to preserve any symbols from external
+ # packages.
+ $self->_reload_classes(\@tables, 0);
# Drop temporary cache
delete $self->{_cache};
}
sub _reload_classes {
- my ($self, @tables) = @_;
+ my ($self, $tables, $unload) = @_;
+
+ my @tables = @$tables;
+ $unload = 1 unless defined $unload;
# so that we don't repeat custom sections
@INC = grep $_ ne $self->dump_directory, @INC;
local *Class::C3::reinitialize = sub {};
use warnings;
- Class::Unload->unload($class);
+ Class::Unload->unload($class) if $unload;
my ($source, $resultset_class);
if (
($source = $have_source{$moniker})
&& ($resultset_class ne 'DBIx::Class::ResultSet')
) {
my $has_file = Class::Inspector->loaded_filename($resultset_class);
- Class::Unload->unload($resultset_class);
- $self->ensure_class_loaded($resultset_class) if $has_file;
+ Class::Unload->unload($resultset_class) if $unload;
+ $self->_reload_class($resultset_class) if $has_file;
}
- $self->ensure_class_loaded($class);
+ $self->_reload_class($class);
}
push @to_register, [$moniker, $class];
}
}
}
+# We use this instead of ensure_class_loaded when there are package symbols we
+# want to preserve.
+sub _reload_class {
+ my ($self, $class) = @_;
+
+ my $class_path = $self->_class_path($class);
+ delete $INC{ $class_path };
+ eval "require $class;";
+}
+
sub _get_dump_filename {
my ($self, $class) = (@_);
}
$text .= $self->_sig_comment(
- $DBIx::Class::Schema::Loader::VERSION,
+ $self->version_to_dump,
POSIX::strftime('%Y-%m-%d %H:%M:%S', localtime)
);
my $tbl_uniq_info = $self->_table_uniq_info($table);
my $local_moniker = $self->monikers->{$table};
- my $rel_stmts = $self->{relbuilder}->generate_code($local_moniker, $tbl_fk_info, $tbl_uniq_info);
+ my $rel_stmts = $self->_relbuilder->generate_code($local_moniker, $tbl_fk_info, $tbl_uniq_info);
foreach my $src_class (sort keys %$rel_stmts) {
my $src_stmts = $rel_stmts->{$src_class};
my $self = shift;
my $class = shift;
my $method = shift;
-
+ if ( $method eq 'table' ) {
+ my ($table) = @_;
+ $self->_pod( $class, "=head1 NAME" );
+ my $table_descr = $class;
+ if ( $self->can('_table_comment') ) {
+ my $comment = $self->_table_comment($table);
+ $table_descr .= " - " . $comment if $comment;
+ }
+ $self->{_class2table}{ $class } = $table;
+ $self->_pod( $class, $table_descr );
+ $self->_pod_cut( $class );
+ } elsif ( $method eq 'add_columns' ) {
+ $self->_pod( $class, "=head1 ACCESSORS" );
+ my $i = 0;
+ foreach ( @_ ) {
+ $i++;
+ next unless $i % 2;
+ $self->_pod( $class, '=head2 ' . $_ );
+ my $comment;
+ $comment = $self->_column_comment( $self->{_class2table}{$class}, ($i - 1) / 2 + 1 ) if $self->can('_column_comment');
+ $self->_pod( $class, $comment ) if $comment;
+ }
+ $self->_pod_cut( $class );
+ } elsif ( $method =~ /^(belongs_to|has_many|might_have)$/ ) {
+ $self->_pod( $class, "=head1 RELATIONS" ) unless $self->{_relations_started} { $class } ;
+ my ( $accessor, $rel_class ) = @_;
+ $self->_pod( $class, "=head2 $accessor" );
+ $self->_pod( $class, 'Type: ' . $method );
+ $self->_pod( $class, "Related object: L<$rel_class>" );
+ $self->_pod_cut( $class );
+ $self->{_relations_started} { $class } = 1;
+ }
my $args = dump(@_);
$args = '(' . $args . ')' if @_ < 2;
my $stmt = $method . $args . q{;};
warn qq|$class\->$stmt\n| if $self->debug;
$self->_raw_stmt($class, '__PACKAGE__->' . $stmt);
+ return;
}
+ # Stores a POD documentation
+ sub _pod {
+ my ($self, $class, $stmt) = @_;
+ $self->_raw_stmt( $class, "\n" . $stmt );
+ }
+
+ sub _pod_cut {
+ my ($self, $class ) = @_;
+ $self->_raw_stmt( $class, "\n=cut\n" );
+ }
+
+
# Store a raw source line for a class (for dumping purposes)
sub _raw_stmt {
my ($self, $class, $stmt) = @_;
$self->drop_tables;
}
+ # defined in sub create
+ my (@statements, @statements_reltests, @statements_advanced,
+ @statements_advanced_sqlite, @statements_inline_rels,
+ @statements_implicit_rels);
+
sub setup_schema {
my $self = shift;
my @connect_info = @_;
my $file_count;
find sub { return if -d; $file_count++ }, $DUMP_DIR;
- is $file_count, 34, 'correct number of files generated';
- exit if $file_count != 34;
+ my $expected_count = 34;
+
+ $expected_count += @{ $self->{extra}{drop} || [] };
+
+ $expected_count -= grep /CREATE TABLE/, @statements_inline_rels
+ if $self->{no_inline_rels};
+
+ $expected_count -= grep /CREATE TABLE/, @statements_implicit_rels
+ if $self->{no_implicit_rels};
+
+ is $file_count, $expected_count, 'correct number of files generated';
+
+ exit if $file_count != $expected_count;
- my $warn_count = 2;
+ my $warn_count = 0;
$warn_count++ if grep /ResultSetManager/, @loader_warnings;
if($self->{skip_rels}) {
my $before_digest = $digest->digest;
my $dbh = $self->dbconnect(1);
- $dbh->do($_) for @statements_rescan;
+
+ {
+ # Silence annoying but harmless postgres "NOTICE: CREATE TABLE..."
+ local $SIG{__WARN__} = sub {
+ my $msg = shift;
+ print STDERR $msg unless $msg =~ m{^NOTICE:\s+CREATE TABLE};
+ };
+
+ $dbh->do($_) for @statements_rescan;
+ }
+
$dbh->disconnect;
sleep 1;
$self->{_created} = 1;
my $make_auto_inc = $self->{auto_inc_cb} || sub {};
- my @statements = (
+ @statements = (
qq{
CREATE TABLE loader_test1s (
id $self->{auto_inc_pk},
},
);
- my @statements_reltests = (
+ @statements_reltests = (
qq{
CREATE TABLE loader_test3 (
id INTEGER NOT NULL PRIMARY KEY,
q{ INSERT INTO loader_test34 (id,rel1) VALUES (1,2) },
);
- my @statements_advanced = (
+ @statements_advanced = (
qq{
CREATE TABLE loader_test10 (
id10 $self->{auto_inc_pk},
q{ REFERENCES loader_test11 (id11) }),
);
- my @statements_advanced_sqlite = (
+ @statements_advanced_sqlite = (
qq{
CREATE TABLE loader_test10 (
id10 $self->{auto_inc_pk},
q{ loader_test11 INTEGER REFERENCES loader_test11 (id11) }),
);
- my @statements_inline_rels = (
+ @statements_inline_rels = (
qq{
CREATE TABLE loader_test12 (
id INTEGER NOT NULL PRIMARY KEY,
);
- my @statements_implicit_rels = (
+ @statements_implicit_rels = (
qq{
CREATE TABLE loader_test14 (
id INTEGER NOT NULL PRIMARY KEY,