... | ... |
@@ -1,4 +1,5 @@ |
1 | 1 |
0.1659 |
2 |
+ - EXPERIMETAL fork safety implementaion. |
|
2 | 3 |
- removed EXPERIMENTAL selection |
3 | 4 |
- added select() all_column option |
4 | 5 |
0.1658 |
... | ... |
@@ -1,6 +1,6 @@ |
1 | 1 |
package DBIx::Custom; |
2 | 2 |
|
3 |
-our $VERSION = '0.1658'; |
|
3 |
+our $VERSION = '0.1659'; |
|
4 | 4 |
|
5 | 5 |
use 5.008001; |
6 | 6 |
use strict; |
... | ... |
@@ -20,7 +20,7 @@ use DBIx::Custom::Util; |
20 | 20 |
use Encode qw/encode_utf8 decode_utf8/; |
21 | 21 |
|
22 | 22 |
__PACKAGE__->attr( |
23 |
- [qw/data_source dbh password user/], |
|
23 |
+ [qw/data_source password pid user/], |
|
24 | 24 |
cache => 1, |
25 | 25 |
dbi_option => sub { {} }, |
26 | 26 |
default_dbi_option => sub {{ |
... | ... |
@@ -35,6 +35,29 @@ __PACKAGE__->attr( |
35 | 35 |
stash => sub { {} } |
36 | 36 |
); |
37 | 37 |
|
38 |
+sub dbh { |
|
39 |
+ my $self = shift; |
|
40 |
+ |
|
41 |
+ if (@_) { |
|
42 |
+ $self->{dbh} = $_[0]; |
|
43 |
+ return $self; |
|
44 |
+ } |
|
45 |
+ else { |
|
46 |
+ my $pid = $$; |
|
47 |
+ if ($self->pid eq $pid) { |
|
48 |
+ return $self->{dbh}; |
|
49 |
+ } |
|
50 |
+ else { |
|
51 |
+ # Create new connection in child process |
|
52 |
+ croak "Process is forked in transaction" |
|
53 |
+ unless $self->{dbh}->{AutoCommit}; |
|
54 |
+ $self->pid($pid); |
|
55 |
+ $self->{dbh}->{InactiveDestroy} = 1; |
|
56 |
+ return $self->{dbh} = $self->_connect; |
|
57 |
+ } |
|
58 |
+ } |
|
59 |
+} |
|
60 |
+ |
|
38 | 61 |
__PACKAGE__->attr( |
39 | 62 |
cache_method => sub { |
40 | 63 |
sub { |
... | ... |
@@ -157,31 +180,14 @@ sub method { |
157 | 180 |
sub connect { |
158 | 181 |
my $self = ref $_[0] ? shift : shift->new(@_);; |
159 | 182 |
|
160 |
- # Attributes |
|
161 |
- my $data_source = $self->data_source; |
|
162 |
- croak qq{"data_source" must be specified to connect()"} |
|
163 |
- unless $data_source; |
|
164 |
- my $user = $self->user; |
|
165 |
- my $password = $self->password; |
|
166 |
- my $dbi_option = {%{$self->dbi_options}, %{$self->dbi_option}}; |
|
167 |
- |
|
168 |
- # Connect |
|
169 |
- my $dbh = eval {DBI->connect( |
|
170 |
- $data_source, |
|
171 |
- $user, |
|
172 |
- $password, |
|
173 |
- { |
|
174 |
- %{$self->default_dbi_option}, |
|
175 |
- %$dbi_option |
|
176 |
- } |
|
177 |
- )}; |
|
178 |
- |
|
179 |
- # Connect error |
|
180 |
- croak $@ if $@; |
|
183 |
+ my $dbh = $self->_connect; |
|
181 | 184 |
|
182 | 185 |
# Database handle |
183 | 186 |
$self->dbh($dbh); |
184 | 187 |
|
188 |
+ # Process ID |
|
189 |
+ $self->pid($$); |
|
190 |
+ |
|
185 | 191 |
return $self; |
186 | 192 |
} |
187 | 193 |
|
... | ... |
@@ -1126,6 +1132,34 @@ sub _bind { |
1126 | 1132 |
return \@bind; |
1127 | 1133 |
} |
1128 | 1134 |
|
1135 |
+sub _connect { |
|
1136 |
+ my $self = shift; |
|
1137 |
+ |
|
1138 |
+ # Attributes |
|
1139 |
+ my $data_source = $self->data_source; |
|
1140 |
+ croak qq{"data_source" must be specified to connect()"} |
|
1141 |
+ unless $data_source; |
|
1142 |
+ my $user = $self->user; |
|
1143 |
+ my $password = $self->password; |
|
1144 |
+ my $dbi_option = {%{$self->dbi_options}, %{$self->dbi_option}}; |
|
1145 |
+ |
|
1146 |
+ # Connect |
|
1147 |
+ my $dbh = eval {DBI->connect( |
|
1148 |
+ $data_source, |
|
1149 |
+ $user, |
|
1150 |
+ $password, |
|
1151 |
+ { |
|
1152 |
+ %{$self->default_dbi_option}, |
|
1153 |
+ %$dbi_option |
|
1154 |
+ } |
|
1155 |
+ )}; |
|
1156 |
+ |
|
1157 |
+ # Connect error |
|
1158 |
+ croak $@ if $@; |
|
1159 |
+ |
|
1160 |
+ return $dbh; |
|
1161 |
+} |
|
1162 |
+ |
|
1129 | 1163 |
sub _croak { |
1130 | 1164 |
my ($self, $error, $append) = @_; |
1131 | 1165 |
$append ||= ""; |
... | ... |
@@ -1434,13 +1468,6 @@ default to 1. |
1434 | 1468 |
|
1435 | 1469 |
Data source, used when C<connect()> is executed. |
1436 | 1470 |
|
1437 |
-=head2 C<dbh> |
|
1438 |
- |
|
1439 |
- my $dbh = $dbi->dbh; |
|
1440 |
- $dbi = $dbi->dbh($dbh); |
|
1441 |
- |
|
1442 |
-Database handle of L<DBI>. |
|
1443 |
- |
|
1444 | 1471 |
=head2 C<dbi_option> |
1445 | 1472 |
|
1446 | 1473 |
my $dbi_option = $dbi->dbi_option; |
... | ... |
@@ -1463,6 +1490,11 @@ default to the following values. |
1463 | 1490 |
AutoCommit => 1, |
1464 | 1491 |
} |
1465 | 1492 |
|
1493 |
+You should not change C<AutoCommit> value directly |
|
1494 |
+to check if the process is in transaction correctly. |
|
1495 |
+L<DBIx::Custom> determin the process is in transaction |
|
1496 |
+if AutoCommit is 0. |
|
1497 |
+ |
|
1466 | 1498 |
=head2 C<filters> |
1467 | 1499 |
|
1468 | 1500 |
my $filters = $dbi->filters; |
... | ... |
@@ -1562,6 +1594,16 @@ instead of suger methods. |
1562 | 1594 |
|
1563 | 1595 |
$dbi->execute($query, {author => 'Ken', title => '%Perl%'}); |
1564 | 1596 |
|
1597 |
+=head2 C<dbh> |
|
1598 |
+ |
|
1599 |
+ my $dbh = $dbi->dbh; |
|
1600 |
+ $dbi = $dbi->dbh($dbh); |
|
1601 |
+ |
|
1602 |
+Get and set database handle of L<DBI>. |
|
1603 |
+ |
|
1604 |
+If process is changed by forking, new connection is created |
|
1605 |
+and get new database hande ofL<DBI>. This feature is EXPERIMETNAL. |
|
1606 |
+ |
|
1565 | 1607 |
=head2 C<execute> |
1566 | 1608 |
|
1567 | 1609 |
my $result = $dbi->execute($query, param => $params, filter => \@filter); |
... | ... |
@@ -30,6 +30,7 @@ sub connect_info { |
30 | 30 |
my $dbi; |
31 | 31 |
my $dbname; |
32 | 32 |
my $rows; |
33 |
+my $result; |
|
33 | 34 |
|
34 | 35 |
# Constant varialbes for test |
35 | 36 |
my $CREATE_TABLE = { |
... | ... |
@@ -59,6 +60,7 @@ $dbi = DBIx::Custom->connect( |
59 | 60 |
user => $USER, |
60 | 61 |
password => $PASSWORD |
61 | 62 |
); |
63 |
+$dbi->delete_all(table => 'table1'); |
|
62 | 64 |
$dbi->insert(table => 'table1', param => {key1 => 1, key2 => 2}); |
63 | 65 |
$dbi->insert(table => 'table1', param => {key1 => 1, key2 => 4}); |
64 | 66 |
$dbi->insert(table => 'table1', param => {key1 => 1, key2 => 6}); |
... | ... |
@@ -93,3 +95,48 @@ $rows = $dbi->select( |
93 | 95 |
)->fetch_hash_all; |
94 | 96 |
is_deeply($rows, [{key1 => 1, key2 => 2}]); |
95 | 97 |
$dbi->delete_all(table => 'table1'); |
98 |
+ |
|
99 |
+ |
|
100 |
+test 'fork'; |
|
101 |
+{ |
|
102 |
+ $dbi = DBIx::Custom->connect( |
|
103 |
+ data_source => "dbi:mysql:database=$DATABASE", |
|
104 |
+ user => $USER, |
|
105 |
+ password => $PASSWORD |
|
106 |
+ ); |
|
107 |
+ $dbi->delete_all(table => 'table1'); |
|
108 |
+ $dbi->insert(table => 'table1', param => {key1 => 1, key2 => 2}); |
|
109 |
+ die "Can't fork" unless defined (my $pid = fork); |
|
110 |
+ |
|
111 |
+ if ($pid) { |
|
112 |
+ # Parent |
|
113 |
+ my $result = $dbi->select(table => 'table1'); |
|
114 |
+ is_deeply($result->fetch_hash_first, {key1 => 1, key2 => 2}); |
|
115 |
+ } |
|
116 |
+ else { |
|
117 |
+ # Child |
|
118 |
+ my $result = $dbi->select(table => 'table1'); |
|
119 |
+ die "Not OK" unless $result->fetch_hash_first->{key1} == 1; |
|
120 |
+ } |
|
121 |
+} |
|
122 |
+ |
|
123 |
+test 'fork in transaction'; |
|
124 |
+{ |
|
125 |
+ $dbi = DBIx::Custom->connect( |
|
126 |
+ data_source => "dbi:mysql:database=$DATABASE", |
|
127 |
+ user => $USER, |
|
128 |
+ password => $PASSWORD |
|
129 |
+ ); |
|
130 |
+ |
|
131 |
+ $dbi->begin_work; |
|
132 |
+ die "Can't fork" unless defined (my $pid = fork); |
|
133 |
+ |
|
134 |
+ if ($pid) { |
|
135 |
+ # Parent |
|
136 |
+ } |
|
137 |
+ else { |
|
138 |
+ # Child |
|
139 |
+ eval {$dbi->select(table => 'table1') }; |
|
140 |
+ die "Not OK" unless $@ =~ /transaction/; |
|
141 |
+ } |
|
142 |
+} |