X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FStorage%2FDBI.pm;h=e1d50efce1332bc461370323b876c021aab0a96f;hb=40dce2a5f0f24e152fe1773760904619804abb1f;hp=37b586e3b83a9df65fb05614fd9f2aae1012fbda;hpb=d4f16b21abb7a541e0c44bfe3ec68309a8d722fa;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Storage/DBI.pm b/lib/DBIx/Class/Storage/DBI.pm index 37b586e..e1d50ef 100644 --- a/lib/DBIx/Class/Storage/DBI.pm +++ b/lib/DBIx/Class/Storage/DBI.pm @@ -3,18 +3,19 @@ package DBIx::Class::Storage::DBI; use base 'DBIx::Class::Storage'; -use strict; +use strict; use warnings; use DBI; use SQL::Abstract::Limit; use DBIx::Class::Storage::DBI::Cursor; use DBIx::Class::Storage::Statistics; use IO::File; +use Scalar::Util 'blessed'; -__PACKAGE__->mk_group_accessors( - 'simple' => - qw/_connect_info _dbh _sql_maker _sql_maker_opts _conn_pid _conn_tid - cursor on_connect_do transaction_depth/ +__PACKAGE__->mk_group_accessors('simple' => + qw/_connect_info _dbi_connect_info _dbh _sql_maker _sql_maker_opts + _conn_pid _conn_tid disable_sth_caching cursor on_connect_do + transaction_depth/ ); BEGIN { @@ -58,9 +59,10 @@ WHERE ROW_NUM BETWEEN $offset AND $last # While we're at it, this should make LIMIT queries more efficient, # without digging into things too deeply +use Scalar::Util 'blessed'; sub _find_syntax { my ($self, $syntax) = @_; - my $dbhname = ref $syntax eq 'HASH' ? $syntax->{Driver}{Name} : ''; + my $dbhname = blessed($syntax) ? $syntax->{Driver}{Name} : $syntax; if(ref($self) && $dbhname && $dbhname eq 'DB2') { return 'RowNumberOver'; } @@ -236,9 +238,18 @@ sub _join_condition { if (ref $cond eq 'HASH') { my %j; for (keys %$cond) { - my $x = '= '.$self->_quote($cond->{$_}); $j{$_} = \$x; + my $v = $cond->{$_}; + if (ref $v) { + # XXX no throw_exception() in this package and croak() fails with strange results + Carp::croak(ref($v) . qq{ reference arguments are not supported in JOINS - try using \"..." instead'}) + if ref($v) ne 'SCALAR'; + $j{$_} = $v; + } + else { + my $x = '= '.$self->_quote($v); $j{$_} = \$x; + } }; - return $self->_recurse_where(\%j); + return scalar($self->_recurse_where(\%j)); } elsif (ref $cond eq 'ARRAY') { return join(' OR ', map { $self->_join_condition($_) } @$cond); } else { @@ -304,6 +315,8 @@ sub new { $new->cursor("DBIx::Class::Storage::DBI::Cursor"); $new->transaction_depth(0); $new->_sql_maker_opts({}); + $new->{_in_dbh_do} = 0; + $new->{_dbh_gen} = 0; $new; } @@ -318,7 +331,13 @@ C here. The arrayref can either contain the same set of arguments one would normally pass to L, or a lone code reference which returns -a connected database handle. +a connected database handle. Please note that the L docs +recommend that you always explicitly set C to either +C<0> or C<1>. L further recommends that it be set +to C<1>, and that you perform transactions via our L +method. L will set it to C<1> if you do not do explicitly +set it to zero. This is the default for most DBDs. See below for more +details. In either case, if the final argument in your connect_info happens to be a hashref, C will look there for several @@ -332,6 +351,11 @@ This can be set to an arrayref of literal sql statements, which will be executed immediately after making the connection to the database every time we [re-]connect. +=item disable_sth_caching + +If set to a true value, this option will disable the caching of +statement handles via L. + =item limit_dialect Sets the limit dialect. This is useful for JDBC-bridge among others @@ -372,6 +396,21 @@ might not work very well, YMMV. If you don't use a subref, DBIC will force this setting for you anyways. Setting HandleError to anything other than simple exception object wrapper might cause problems too. +Another Important Note: + +DBIC can do some wonderful magic with handling exceptions, +disconnections, and transactions when you use C +combined with C for transaction support. + +If you set C in your connect info, then you are always +in an assumed transaction between commits, and you're telling us you'd +like to manage that manually. A lot of DBIC's magic protections +go away. We can't protect you from exceptions due to database +disconnects because we don't know anything about how to restart your +transactions. You're on your own for handling all sorts of exceptional +cases if you choose the C path, just as you would +be with raw DBI. + Examples: # Simple SQLite connection @@ -386,7 +425,7 @@ Examples: 'dbi:Pg:dbname=foo', 'postgres', 'my_pg_password', - { AutoCommit => 0 }, + { AutoCommit => 1 }, { quote_char => q{"}, name_sep => q{.} }, ] ); @@ -397,7 +436,7 @@ Examples: 'dbi:Pg:dbname=foo', 'postgres', 'my_pg_password', - { AutoCommit => 0, quote_char => q{"}, name_sep => q{.} }, + { AutoCommit => 1, quote_char => q{"}, name_sep => q{.} }, ] ); @@ -409,6 +448,7 @@ Examples: quote_char => q{`}, name_sep => q{@}, on_connect_do => ['SET search_path TO myschema,otherschema,public'], + disable_sth_caching => 1, }, ] ); @@ -424,12 +464,16 @@ sub connect_info { # the new set of options $self->_sql_maker(undef); $self->_sql_maker_opts({}); + $self->_connect_info([@$info_arg]); # copy for _connect_info + + my $dbi_info = [@$info_arg]; # copy for _dbi_connect_info - my $info = [ @$info_arg ]; # copy because we can alter it - my $last_info = $info->[-1]; + my $last_info = $dbi_info->[-1]; if(ref $last_info eq 'HASH') { - if(my $on_connect_do = delete $last_info->{on_connect_do}) { - $self->on_connect_do($on_connect_do); + for my $storage_opt (qw/on_connect_do disable_sth_caching/) { + if(my $value = delete $last_info->{$storage_opt}) { + $self->$storage_opt($value); + } } for my $sql_maker_opt (qw/limit_dialect quote_char name_sep/) { if(my $opt_val = delete $last_info->{$sql_maker_opt}) { @@ -438,10 +482,11 @@ sub connect_info { } # Get rid of any trailing empty hashref - pop(@$info) if !keys %$last_info; + pop(@$dbi_info) if !keys %$last_info; } + $self->_dbi_connect_info($dbi_info); - $self->_connect_info($info); + $self->_connect_info; } =head2 on_connect_do @@ -482,11 +527,14 @@ sub dbh_do { my $self = shift; my $coderef = shift; - return $coderef->($self, $self->_dbh, @_) if $self->{_in_txn_do}; - ref $coderef eq 'CODE' or $self->throw_exception ('$coderef must be a CODE reference'); + return $coderef->($self, $self->_dbh, @_) if $self->{_in_dbh_do} + || $self->{transaction_depth}; + + local $self->{_in_dbh_do} = 1; + my @result; my $want_array = wantarray; @@ -517,7 +565,7 @@ sub dbh_do { # This is basically a blend of dbh_do above and DBIx::Class::Storage::txn_do. # It also informs dbh_do to bypass itself while under the direction of txn_do, -# via $self->{_in_txn_do} (this saves some redundant eval and errorcheck, etc) +# via $self->{_in_dbh_do} (this saves some redundant eval and errorcheck, etc) sub txn_do { my $self = shift; my $coderef = shift; @@ -525,7 +573,7 @@ sub txn_do { ref $coderef eq 'CODE' or $self->throw_exception ('$coderef must be a CODE reference'); - local $self->{_in_txn_do} = 1; + local $self->{_in_dbh_do} = 1; my @result; my $want_array = wantarray; @@ -588,6 +636,7 @@ sub disconnect { $self->_dbh->rollback unless $self->_dbh->{AutoCommit}; $self->_dbh->disconnect; $self->_dbh(undef); + $self->{_dbh_gen}++; } } @@ -596,7 +645,9 @@ sub connected { if(my $dbh = $self->_dbh) { if(defined $self->_conn_tid && $self->_conn_tid != threads->tid) { - return $self->_dbh(undef); + $self->_dbh(undef); + $self->{_dbh_gen}++; + return; } else { $self->_verify_pid; @@ -616,6 +667,7 @@ sub _verify_pid { $self->_dbh->{InactiveDestroy} = 1; $self->_dbh(undef); + $self->{_dbh_gen}++; return; } @@ -644,7 +696,7 @@ sub dbh { sub _sql_maker_args { my ($self) = @_; - return ( limit_dialect => $self->dbh, %{$self->_sql_maker_opts} ); + return ( bindtype=>'columns', limit_dialect => $self->dbh, %{$self->_sql_maker_opts} ); } sub sql_maker { @@ -657,9 +709,13 @@ sub sql_maker { sub _populate_dbh { my ($self) = @_; - my @info = @{$self->_connect_info || []}; + my @info = @{$self->_dbi_connect_info || []}; $self->_dbh($self->_connect(@info)); + # Always set the transaction depth on connect, since + # there is no transaction in progress by definition + $self->{transaction_depth} = $self->_dbh->{AutoCommit} ? 0 : 1; + if(ref $self eq 'DBIx::Class::Storage::DBI') { my $driver = $self->_dbh->{Driver}->{Name}; if ($self->load_optional_class("DBIx::Class::Storage::DBI::${driver}")) { @@ -700,113 +756,222 @@ sub _connect { $dbh = DBI->connect(@info); $dbh->{RaiseError} = 1; $dbh->{PrintError} = 0; + $dbh->{PrintWarn} = 0; } }; $DBI::connect_via = $old_connect_via if $old_connect_via; - if (!$dbh || $@) { - $self->throw_exception("DBI Connection failed: " . ($@ || $DBI::errstr)); - } + $self->throw_exception("DBI Connection failed: " . ($@||$DBI::errstr)) + if !$dbh || $@; $dbh; } -sub _dbh_txn_begin { - my ($self, $dbh) = @_; - if ($dbh->{AutoCommit}) { - $self->debugobj->txn_begin() - if ($self->debug); - $dbh->begin_work; - } -} sub txn_begin { my $self = shift; - $self->dbh_do($self->can('_dbh_txn_begin')) - if $self->{transaction_depth}++ == 0; -} - -sub _dbh_txn_commit { - my ($self, $dbh) = @_; - if ($self->{transaction_depth} == 0) { - unless ($dbh->{AutoCommit}) { - $self->debugobj->txn_commit() - if ($self->debug); - $dbh->commit; - } - } - else { - if (--$self->{transaction_depth} == 0) { - $self->debugobj->txn_commit() - if ($self->debug); - $dbh->commit; - } + if($self->{transaction_depth}++ == 0) { + $self->debugobj->txn_begin() + if $self->debug; + # this isn't ->_dbh-> because + # we should reconnect on begin_work + # for AutoCommit users + $self->dbh->begin_work; } } sub txn_commit { my $self = shift; - $self->dbh_do($self->can('_dbh_txn_commit')); + if ($self->{transaction_depth} == 1) { + my $dbh = $self->_dbh; + $self->debugobj->txn_commit() + if ($self->debug); + $dbh->commit; + $self->{transaction_depth} = 0 + if $dbh->{AutoCommit}; + } + elsif($self->{transaction_depth} > 1) { + $self->{transaction_depth}-- + } } -sub _dbh_txn_rollback { - my ($self, $dbh) = @_; - if ($self->{transaction_depth} == 0) { - unless ($dbh->{AutoCommit}) { +sub txn_rollback { + my $self = shift; + my $dbh = $self->_dbh; + my $autocommit; + eval { + $autocommit = $dbh->{AutoCommit}; + if ($self->{transaction_depth} == 1) { $self->debugobj->txn_rollback() if ($self->debug); $dbh->rollback; + $self->{transaction_depth} = 0 + if $autocommit; } - } - else { - if (--$self->{transaction_depth} == 0) { - $self->debugobj->txn_rollback() - if ($self->debug); - $dbh->rollback; + elsif($self->{transaction_depth} > 1) { + $self->{transaction_depth}--; } else { die DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION->new; } - } -} - -sub txn_rollback { - my $self = shift; - - eval { $self->dbh_do($self->can('_dbh_txn_rollback')) }; + }; if ($@) { my $error = $@; my $exception_class = "DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION"; $error =~ /$exception_class/ and $self->throw_exception($error); - $self->{transaction_depth} = 0; # ensure that a failed rollback - $self->throw_exception($error); # resets the transaction depth + # ensure that a failed rollback resets the transaction depth + $self->{transaction_depth} = $autocommit ? 0 : 1; + $self->throw_exception($error); } } +# This used to be the top-half of _execute. It was split out to make it +# easier to override in NoBindVars without duping the rest. It takes up +# all of _execute's args, and emits $sql, @bind. +sub _prep_for_execute { + my ($self, $op, $extra_bind, $ident, $args) = @_; + + my ($sql, @bind) = $self->sql_maker->$op($ident, @$args); + unshift(@bind, + map { ref $_ eq 'ARRAY' ? $_ : [ '!!dummy', $_ ] } @$extra_bind) + if $extra_bind; + + return ($sql, \@bind); +} + sub _execute { - my ($self, $op, $extra_bind, $ident, @args) = @_; - my ($sql, @bind) = $self->sql_maker->$op($ident, @args); - unshift(@bind, @$extra_bind) if $extra_bind; + my ($self, $op, $extra_bind, $ident, $bind_attributes, @args) = @_; + + if( blessed($ident) && $ident->isa("DBIx::Class::ResultSource") ) { + $ident = $ident->from(); + } + + my ($sql, $bind) = $self->_prep_for_execute($op, $extra_bind, $ident, \@args); + if ($self->debug) { - my @debug_bind = map { defined $_ ? qq{'$_'} : q{'NULL'} } @bind; + my @debug_bind = + map { defined ($_ && $_->[1]) ? qq{'$_->[1]'} : q{'NULL'} } @$bind; $self->debugobj->query_start($sql, @debug_bind); } + my $sth = eval { $self->sth($sql,$op) }; + $self->throw_exception("no sth generated via sql ($@): $sql") if $@; + + my $rv = eval { + my $placeholder_index = 1; + + foreach my $bound (@$bind) { + my $attributes = {}; + my($column_name, @data) = @$bound; + + if ($bind_attributes) { + $attributes = $bind_attributes->{$column_name} + if defined $bind_attributes->{$column_name}; + } + + foreach my $data (@data) { + $data = ref $data ? ''.$data : $data; # stringify args + + $sth->bind_param($placeholder_index, $data, $attributes); + $placeholder_index++; + } + } + $sth->execute(); + }; + + $self->throw_exception("Error executing '$sql': " . ($@ || $sth->errstr)) + if $@ || !$rv; - if (!$sth || $@) { - $self->throw_exception( - 'no sth generated via sql (' . ($@ || $self->_dbh->errstr) . "): $sql" - ); + if ($self->debug) { + my @debug_bind = + map { defined ($_ && $_->[1]) ? qq{'$_->[1]'} : q{'NULL'} } @$bind; + $self->debugobj->query_end($sql, @debug_bind); } - @bind = map { ref $_ ? ''.$_ : $_ } @bind; # stringify args + return (wantarray ? ($rv, $sth, @$bind) : $rv); +} + +sub insert { + my ($self, $source, $to_insert) = @_; + + my $ident = $source->from; + my $bind_attributes = $self->source_bind_attributes($source); + + eval { $self->_execute('insert' => [], $source, $bind_attributes, $to_insert) }; + $self->throw_exception( + "Couldn't insert ".join(', ', + map "$_ => $to_insert->{$_}", keys %$to_insert + )." into ${ident}: $@" + ) if $@; + + return $to_insert; +} + +## Still not quite perfect, and EXPERIMENTAL +## Currently it is assumed that all values passed will be "normal", i.e. not +## scalar refs, or at least, all the same type as the first set, the statement is +## only prepped once. +sub insert_bulk { + my ($self, $source, $cols, $data) = @_; + my %colvalues; + my $table = $source->from; + @colvalues{@$cols} = (0..$#$cols); + my ($sql, @bind) = $self->sql_maker->insert($table, \%colvalues); + + if ($self->debug) { + my @debug_bind = map { defined $_->[1] ? qq{$_->[1]} : q{'NULL'} } @bind; + $self->debugobj->query_start($sql, @debug_bind); + } + my $sth = $self->sth($sql); + +# @bind = map { ref $_ ? ''.$_ : $_ } @bind; # stringify args + my $rv; + + ## This must be an arrayref, else nothing works! + + my $tuple_status = []; + + ##use Data::Dumper; + ##print STDERR Dumper( $data, $sql, [@bind] ); + if ($sth) { + my $time = time(); - $rv = eval { $sth->execute(@bind) }; - if ($@ || !$rv) { - $self->throw_exception("Error executing '$sql': ".($@ || $sth->errstr)); + ## Get the bind_attributes, if any exist + my $bind_attributes = $self->source_bind_attributes($source); + + ## Bind the values and execute + $rv = eval { + + my $placeholder_index = 1; + + foreach my $bound (@bind) { + + my $attributes = {}; + my ($column_name, $data_index) = @$bound; + + if( $bind_attributes ) { + $attributes = $bind_attributes->{$column_name} + if defined $bind_attributes->{$column_name}; + } + + my @data = map { $_->[$data_index] } @$data; + + $sth->bind_param_array( $placeholder_index, [@data], $attributes ); + $placeholder_index++; + } + $sth->execute_array( {ArrayTupleStatus => $tuple_status} ); + + }; + + if ($@ || !defined $rv) { + my $errors = ''; + foreach my $tuple (@$tuple_status) { + $errors .= "\n" . $tuple->[1] if(ref $tuple); + } + $self->throw_exception("Error executing '$sql': ".($@ || $errors)); } } else { $self->throw_exception("'$sql' did not generate a statement."); @@ -818,22 +983,22 @@ sub _execute { return (wantarray ? ($rv, $sth, @bind) : $rv); } -sub insert { - my ($self, $ident, $to_insert) = @_; - $self->throw_exception( - "Couldn't insert ".join(', ', - map "$_ => $to_insert->{$_}", keys %$to_insert - )." into ${ident}" - ) unless ($self->_execute('insert' => [], $ident, $to_insert)); - return $to_insert; -} - sub update { - return shift->_execute('update' => [], @_); + my $self = shift @_; + my $source = shift @_; + my $bind_attributes = $self->source_bind_attributes($source); + + return $self->_execute('update' => [], $source, $bind_attributes, @_); } + sub delete { - return shift->_execute('delete' => [], @_); + my $self = shift @_; + my $source = shift @_; + + my $bind_attrs = {}; ## If ever it's needed... + + return $self->_execute('delete' => [], $source, $bind_attrs, @_); } sub _select { @@ -849,7 +1014,8 @@ sub _select { ($order ? (order_by => $order) : ()) }; } - my @args = ('select', $attrs->{bind}, $ident, $select, $condition, $order); + my $bind_attrs = {}; ## Future support + my @args = ('select', $attrs->{bind}, $ident, $bind_attrs, $select, $condition, $order); if ($attrs->{software_limit} || $self->sql_maker->_default_limit_syntax eq "GenericSubQ") { $attrs->{software_limit} = 1; @@ -861,6 +1027,20 @@ sub _select { return $self->_execute(@args); } +sub source_bind_attributes { + my ($self, $source) = @_; + + my $bind_attributes; + foreach my $column ($source->columns) { + + my $data_type = $source->column_info($column)->{data_type} || ''; + $bind_attributes->{$column} = $self->bind_attribute_by_data_type($data_type) + if $data_type; + } + + return $bind_attributes; +} + =head2 select =over 4 @@ -902,8 +1082,17 @@ Returns a L sth (statement handle) for the supplied SQL. sub _dbh_sth { my ($self, $dbh, $sql) = @_; + # 3 is the if_active parameter which avoids active sth re-use - $dbh->prepare_cached($sql, {}, 3); + my $sth = $self->disable_sth_caching + ? $dbh->prepare($sql) + : $dbh->prepare_cached($sql, {}, 3); + + # XXX You would think RaiseError would make this impossible, + # but apparently that's not true :( + die $dbh->errstr if !$sth; + + $sth; } sub sth { @@ -936,18 +1125,12 @@ sub _dbh_columns_info_for { } my %result; - my $sth = $dbh->prepare("SELECT * FROM $table WHERE 1=0"); + my $sth = $dbh->prepare($self->sql_maker->select($table, undef, \'1 = 0')); $sth->execute; my @columns = @{$sth->{NAME_lc}}; for my $i ( 0 .. $#columns ){ my %column_info; - my $type_num = $sth->{TYPE}->[$i]; - my $type_name; - if(defined $type_num && $dbh->can('type_info')) { - my $type_info = $dbh->type_info($type_num); - $type_name = $type_info->{TYPE_NAME} if $type_info; - } - $column_info{data_type} = $type_name ? $type_name : $type_num; + $column_info{data_type} = $sth->{TYPE}->[$i]; $column_info{size} = $sth->{PRECISION}->[$i]; $column_info{is_nullable} = $sth->{NULLABLE}->[$i] ? 1 : 0; @@ -958,6 +1141,18 @@ sub _dbh_columns_info_for { $result{$columns[$i]} = \%column_info; } + $sth->finish; + + foreach my $col (keys %result) { + my $colinfo = $result{$col}; + my $type_num = $colinfo->{data_type}; + my $type_name; + if(defined $type_num && $dbh->can('type_info')) { + my $type_info = $dbh->type_info($type_num); + $type_name = $type_info->{TYPE_NAME} if $type_info; + $colinfo->{data_type} = $type_name if $type_name; + } + } return \%result; } @@ -992,11 +1187,25 @@ Returns the database driver name. sub sqlt_type { shift->dbh->{Driver}->{Name} } +=head2 bind_attribute_by_data_type + +Given a datatype from column info, returns a database specific bind attribute for +$dbh->bind_param($val,$attribute) or nothing if we will let the database planner +just handle it. + +Generally only needed for special case column types, like bytea in postgres. + +=cut + +sub bind_attribute_by_data_type { + return; +} + =head2 create_ddl_dir (EXPERIMENTAL) =over 4 -=item Arguments: $schema \@databases, $version, $directory, $sqlt_args +=item Arguments: $schema \@databases, $version, $directory, $preversion, $sqlt_args =back @@ -1010,7 +1219,7 @@ across all databases, or fully handle complex relationships. sub create_ddl_dir { - my ($self, $schema, $databases, $version, $dir, $sqltargs) = @_; + my ($self, $schema, $databases, $version, $dir, $preversion, $sqltargs) = @_; if(!$dir || !-d $dir) { @@ -1022,15 +1231,20 @@ sub create_ddl_dir $version ||= $schema->VERSION || '1.x'; $sqltargs = { ( add_drop_table => 1 ), %{$sqltargs || {}} }; - eval "use SQL::Translator"; - $self->throw_exception("Can't deploy without SQL::Translator: $@") if $@; + $self->throw_exception(q{Can't create a ddl file without SQL::Translator 0.08: '} + . $self->_check_sqlt_message . q{'}) + if !$self->_check_sqlt_version; - my $sqlt = SQL::Translator->new($sqltargs); + my $sqlt = SQL::Translator->new({ +# debug => 1, + add_drop_table => 1, + }); foreach my $db (@$databases) { $sqlt->reset(); $sqlt->parser('SQL::Translator::Parser::DBIx::Class'); # $sqlt->parser_args({'DBIx::Class' => $schema); + $sqlt = $self->configure_sqlt($sqlt, $db); $sqlt->data($schema); $sqlt->producer($db); @@ -1038,24 +1252,92 @@ sub create_ddl_dir my $filename = $schema->ddl_filename($db, $dir, $version); if(-e $filename) { - $self->throw_exception("$filename already exists, skipping $db"); + warn("$filename already exists, skipping $db"); next; } - open($file, ">$filename") - or $self->throw_exception("Can't open $filename for writing ($!)"); + my $output = $sqlt->translate; -#use Data::Dumper; -# print join(":", keys %{$schema->source_registrations}); -# print Dumper($sqlt->schema); if(!$output) { - $self->throw_exception("Failed to translate to $db. (" . $sqlt->error . ")"); + warn("Failed to translate to $db, skipping. (" . $sqlt->error . ")"); next; } + if(!open($file, ">$filename")) + { + $self->throw_exception("Can't open $filename for writing ($!)"); + next; + } print $file $output; close($file); + + if($preversion) + { + require SQL::Translator::Diff; + + my $prefilename = $schema->ddl_filename($db, $dir, $preversion); +# print "Previous version $prefilename\n"; + if(!-e $prefilename) + { + warn("No previous schema file found ($prefilename)"); + next; + } + #### We need to reparse the SQLite file we just wrote, so that + ## Diff doesnt get all confoosed, and Diff is *very* confused. + ## FIXME: rip Diff to pieces! +# my $target_schema = $sqlt->schema; +# unless ( $target_schema->name ) { +# $target_schema->name( $filename ); +# } + my @input; + push @input, {file => $prefilename, parser => $db}; + push @input, {file => $filename, parser => $db}; + my ( $source_schema, $source_db, $target_schema, $target_db ) = map { + my $file = $_->{'file'}; + my $parser = $_->{'parser'}; + + my $t = SQL::Translator->new; + $t->debug( 0 ); + $t->trace( 0 ); + $t->parser( $parser ) or die $t->error; + my $out = $t->translate( $file ) or die $t->error; + my $schema = $t->schema; + unless ( $schema->name ) { + $schema->name( $file ); + } + ($schema, $parser); + } @input; + + my $diff = SQL::Translator::Diff::schema_diff($source_schema, $db, + $target_schema, $db, + {} + ); + my $difffile = $schema->ddl_filename($db, $dir, $version, $preversion); + print STDERR "Diff: $difffile: $db, $dir, $version, $preversion \n"; + if(-e $difffile) + { + warn("$difffile already exists, skipping"); + next; + } + if(!open $file, ">$difffile") + { + $self->throw_exception("Can't write to $difffile ($!)"); + next; + } + print $file $diff; + close($file); + } } +} +sub configure_sqlt() { + my $self = shift; + my $tr = shift; + my $db = shift || $self->sqlt_type; + if ($db eq 'PostgreSQL') { + $tr->quote_table_names(0); + $tr->quote_field_names(0); + } + return $tr; } =head2 deployment_statements @@ -1088,33 +1370,36 @@ sub deployment_statements { $type ||= $self->sqlt_type; $version ||= $schema->VERSION || '1.x'; $dir ||= './'; - eval "use SQL::Translator"; - if(!$@) - { - eval "use SQL::Translator::Parser::DBIx::Class;"; - $self->throw_exception($@) if $@; - eval "use SQL::Translator::Producer::${type};"; - $self->throw_exception($@) if $@; - my $tr = SQL::Translator->new(%$sqltargs); - SQL::Translator::Parser::DBIx::Class::parse( $tr, $schema ); - return "SQL::Translator::Producer::${type}"->can('produce')->($tr); - } - my $filename = $schema->ddl_filename($type, $dir, $version); - if(!-f $filename) + if(-f $filename) { -# $schema->create_ddl_dir([ $type ], $version, $dir, $sqltargs); - $self->throw_exception("No SQL::Translator, and no Schema file found, aborting deploy"); - return; + my $file; + open($file, "<$filename") + or $self->throw_exception("Can't open $filename ($!)"); + my @rows = <$file>; + close($file); + return join('', @rows); } - my $file; - open($file, "<$filename") - or $self->throw_exception("Can't open $filename ($!)"); - my @rows = <$file>; - close($file); - return join('', @rows); - + $self->throw_exception(q{Can't deploy without SQL::Translator 0.08: '} + . $self->_check_sqlt_message . q{'}) + if !$self->_check_sqlt_version; + + require SQL::Translator::Parser::DBIx::Class; + eval qq{use SQL::Translator::Producer::${type}}; + $self->throw_exception($@) if $@; + + # sources needs to be a parser arg, but for simplicty allow at top level + # coming in + $sqltargs->{parser_args}{sources} = delete $sqltargs->{sources} + if exists $sqltargs->{sources}; + + my $tr = SQL::Translator->new(%$sqltargs); + SQL::Translator::Parser::DBIx::Class::parse( $tr, $schema ); + return "SQL::Translator::Producer::${type}"->can('produce')->($tr); + + return; + } sub deploy { @@ -1128,7 +1413,7 @@ sub deploy { next if($_ =~ /^COMMIT/m); next if $_ =~ /^\s+$/; # skip whitespace only $self->debugobj->query_start($_) if $self->debug; - $self->dbh->do($_) or warn "SQL was:\n $_"; # XXX exceptions? + $self->dbh->do($_); # shouldn't be using ->dbh ? $self->debugobj->query_end($_) if $self->debug; } } @@ -1168,6 +1453,22 @@ sub build_datetime_parser { return $type; } +{ + my $_check_sqlt_version; # private + my $_check_sqlt_message; # private + sub _check_sqlt_version { + return $_check_sqlt_version if defined $_check_sqlt_version; + eval 'use SQL::Translator 0.08'; + $_check_sqlt_message = $@ ? $@ : ''; + $_check_sqlt_version = $@ ? 0 : 1; + } + + sub _check_sqlt_message { + _check_sqlt_version if !defined $_check_sqlt_message; + $_check_sqlt_message; + } +} + sub DESTROY { my $self = shift; return if !$self->_dbh;