File Coverage

blib/lib/DBIx/QuickORM/Dialect/MySQL.pm
Criterion Covered Total %
statement 47 226 20.8
branch 2 72 2.7
condition 1 48 2.0
subroutine 14 39 35.9
pod 0 24 0.0
total 64 409 15.6


line stmt bran cond sub pod time code
1             package DBIx::QuickORM::Dialect::MySQL;
2 1     1   8 use strict;
  1         3  
  1         49  
3 1     1   6 use warnings;
  1         32  
  1         110  
4              
5             our $VERSION = '0.000019';
6              
7 1     1   8 use Carp qw/croak/;
  1         3  
  1         105  
8 1     1   9 use Scalar::Util qw/blessed/;
  1         2  
  1         63  
9 1     1   7 use DBIx::QuickORM::Util qw/column_key load_class/;
  1         2  
  1         11  
10 1     1   44 use DBIx::QuickORM::Affinity qw/affinity_from_type/;
  1         2  
  1         62  
11              
12 1     1   7 use DBI();
  1         3  
  1         23  
13              
14 1     1   6 use DBIx::QuickORM::Schema;
  1         2  
  1         28  
15 1     1   5 use DBIx::QuickORM::Schema::Table;
  1         2  
  1         53  
16 1     1   6 use DBIx::QuickORM::Schema::Table::Column;
  1         1  
  1         75  
17 1     1   7 use DBIx::QuickORM::Schema::View;
  1         2  
  1         43  
18              
19 1     1   7 use parent 'DBIx::QuickORM::Dialect';
  1         2  
  1         10  
20 1         9 use DBIx::QuickORM::Util::HashBase qw{
21             +dbi_driver
22 1     1   94 };
  1         2  
23              
24 0     0 0   sub async_supported { 1 }
25 0     0 0   sub async_cancel_supported { 0 }
26 0 0   0 0   sub async_prepare_args { my ($s, %p) = @_; $s->dbi_driver eq 'DBD::mysql' ? (async => 1) : (mariadb_async => 1) }
  0            
27 0 0   0 0   sub async_ready { my ($s, %p) = @_; $s->dbi_driver eq 'DBD::mysql' ? $p{sth}->mysql_async_ready() : $p{sth}->mariadb_async_ready() }
  0            
28 0 0   0 0   sub async_result { my ($s, %p) = @_; $s->dbi_driver eq 'DBD::mysql' ? $p{sth}->mysql_async_result() : $p{sth}->mariadb_async_result() }
  0            
29 0     0 0   sub async_cancel { croak "Dialect '" . $_[0]->dialect_name . "' does not support canceling async queries" }
30              
31 0   0 0 0   sub start_txn { my ($s, %p) = @_; my $dbh = $p{dbh} // $s->dbh; $dbh->begin_work }
  0            
  0            
32 0   0 0 0   sub commit_txn { my ($s, %p) = @_; my $dbh = $p{dbh} // $s->dbh; $dbh->commit }
  0            
  0            
33 0   0 0 0   sub rollback_txn { my ($s, %p) = @_; my $dbh = $p{dbh} // $s->dbh; $dbh->rollback }
  0            
  0            
34 0   0 0 0   sub create_savepoint { my ($s, %p) = @_; my $dbh = $p{dbh} // $s->dbh; $dbh->do("SAVEPOINT $p{savepoint}") }
  0            
  0            
35 0   0 0 0   sub commit_savepoint { my ($s, %p) = @_; my $dbh = $p{dbh} // $s->dbh; $dbh->do("RELEASE SAVEPOINT $p{savepoint}") }
  0            
  0            
36 0   0 0 0   sub rollback_savepoint { my ($s, %p) = @_; my $dbh = $p{dbh} // $s->dbh; $dbh->do("ROLLBACK TO SAVEPOINT $p{savepoint}") }
  0            
  0            
37              
38             BEGIN {
39 1     1   4 my $mariadb = eval { require DBD::MariaDB; 1 };
  1         9  
  1         3  
40 1         2 my $mysql = eval { require DBD::mysql; 1 };
  1         4  
  1         3  
41              
42 1 50 33     5 croak "You must install either DBD::MariaDB or DBD::mysql" unless $mariadb || $mysql;
43              
44 1 50       2902 *DEFAULT_DBI_DRIVER = $mariadb ? sub() { 'DBD::MariaDB' } : sub() { 'DBD::mysql' };
45             }
46              
47             sub dbi_driver {
48 0     0 0   my $in = shift;
49              
50 0 0         return DEFAULT_DBI_DRIVER() unless blessed($in);
51              
52 0 0         return $in->{+DBI_DRIVER} if $in->{+DBI_DRIVER};
53              
54 0           my $dbh = $in->dbh;
55              
56 0           return $in->{+DBI_DRIVER} = "DBD::" . $dbh->{Driver}->{Name};
57             }
58              
59             sub quote_binary_data {
60 0     0 0   my $self = shift;
61 0           my $driver = $self->dbi_driver;
62 0 0         return undef if $driver eq 'DBD::mysql';
63 0 0         return DBI::SQL_BINARY if $driver eq 'DBD::MariaDB';
64 0           croak "Unknown DBD::Driver '$driver'";
65             }
66              
67             sub init {
68 0     0 0   my $self = shift;
69              
70 0 0         if (blessed($self) eq __PACKAGE__) {
71 0 0         if (my $vendor = $self->db_vendor) {
72 0 0         if (my $class = load_class("DBIx::QuickORM::Dialect::MySQL::${vendor}")) {
    0          
73 0           bless($self, $class);
74 0           return $self->init();
75             }
76             elsif ($@ !~ m{Can't locate DBIx/QuickORM/Dialect/MySQL/${vendor}\.pm in \@INC}) {
77 0           die $@;
78             }
79              
80 0           warn "Could not find vendor specific dialect 'DBIx::QuickORM::Dialect::MySQL::${vendor}', using 'DBIx::QuickORM::Dialect::MySQL'. This can result in degraded capabilities compared to a dedicate dialect\n";
81             }
82             else {
83 0           warn "Could not find vendor specific dialect 'DBIx::QuickORM::Dialect::MySQL::YOUR_VENDOR', using 'DBIx::QuickORM::Dialect::MySQL'. This can result in degraded capabilities compared to a dedicate dialect\n";
84             }
85             }
86              
87 0           return $self->SUPER::init();
88             }
89              
90 0     0 0   sub dialect_name { 'MySQL' }
91              
92             sub dsn_socket_field {
93 0     0 0   my $this = shift;
94 0           my ($driver) = @_;
95              
96 0 0         return 'mariadb_socket' if $driver eq 'DBD::MariaDB';
97 0 0         return 'mysql_socket' if $driver eq 'DBD::mysql';
98              
99 0           $this->SUPER::dsn_socket_field($driver);
100             };
101              
102             sub db_version {
103 0     0 0   my $self = shift;
104              
105 0           my $dbh = $self->{+DBH};
106              
107 0           my $sth = $dbh->prepare("SELECT version()");
108 0           $sth->execute();
109              
110 0           my ($ver) = $sth->fetchrow_array;
111 0           return $ver;
112             }
113              
114             sub db_vendor {
115 0     0 0   my $self = shift;
116              
117 0           my $dbh = $self->{+DBH};
118              
119 0           for my $cmd ('SELECT @@version_comment', "SELECT version()") {
120 0           my $sth = $dbh->prepare($cmd);
121 0           $sth->execute();
122 0           my ($val) = $sth->fetchrow_array;
123              
124 0 0         return 'MariaDB' if $val =~ m/MariaDB/i;
125 0 0         return 'Percona' if $val =~ m/Percona/i;
126 0 0         return 'Community' if $val =~ m/Community/i;
127             }
128              
129 0           my $sth = $dbh->prepare('SHOW VARIABLES LIKE "%version%"');
130 0           $sth->execute();
131              
132 0           while (my @vals = $sth->fetchrow_array) {
133 0           for my $val (@vals) {
134 0 0         return 'MariaDB' if $val =~ m/MariaDB/i;
135 0 0         return 'Percona' if $val =~ m/Percona/i;
136 0 0         return 'Community' if $val =~ m/Community/i;
137             }
138             }
139              
140 0           return undef;
141             }
142              
143             sub upsert_statement {
144 0     0 0   my $self = shift;
145 0           my ($pk) = @_;
146 0           return "ON DUPLICATE KEY UPDATE";
147             }
148              
149              
150             ###############################################################################
151             # {{{ Schema Builder Code
152             ###############################################################################
153              
154             my %TABLE_TYPES = (
155             'BASE TABLE' => 'DBIx::QuickORM::Schema::Table',
156             'VIEW' => 'DBIx::QuickORM::Schema::View',
157             'TEMPORARY' => 'DBIx::QuickORM::Schema::Table',
158             );
159              
160             my %TEMP_TYPES = (
161             'BASE TABLE' => 0,
162             'VIEW' => 0,
163             'TEMPORARY' => 1,
164             );
165              
166             sub build_tables_from_db {
167 0     0 0   my $self = shift;
168 0           my %params = @_;
169              
170 0           my $dbh = $self->{+DBH};
171              
172 0           my $sth = $dbh->prepare('SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = ?');
173 0           $sth->execute($self->{+DB_NAME});
174              
175 0           my %tables;
176              
177 0           while (my ($tname, $type) = $sth->fetchrow_array) {
178 0   0       my $table = {name => $tname, db_name => $tname, is_temp => $TEMP_TYPES{$type} // 0};
179 0   0       my $class = $TABLE_TYPES{$type} // 'DBIx::QuickORM::Schema::Table';
180 0           $params{autofill}->hook(pre_table => {table => $table, class => \$class});
181              
182 0           $table->{columns} = $self->build_columns_from_db($tname, %params);
183 0           $params{autofill}->hook(columns => {columns => $table->{columns}, table => $table});
184              
185 0           $table->{indexes} = $self->build_indexes_from_db($tname, %params);
186 0           $params{autofill}->hook(indexes => {indexes => $table->{indexes}, table => $table});
187              
188 0           @{$table}{qw/primary_key unique _links/} = $self->build_table_keys_from_db($tname, %params);
  0            
189              
190 0           $params{autofill}->hook(post_table => {table => $table, class => \$class});
191 0           $tables{$tname} = $class->new($table);
192 0           $params{autofill}->hook(table => {table => $tables{$tname}});
193             }
194              
195 0           return \%tables;
196             }
197              
198             sub build_table_keys_from_db {
199 0     0 0   my $self = shift;
200 0           my ($table, %params) = @_;
201              
202 0           my $dbh = $self->{+DBH};
203              
204 0           my $sth = $dbh->prepare(<<" EOT");
205             SELECT tco.constraint_name AS con,
206             tco.constraint_type AS type,
207             kcu.column_name AS col,
208             kcu.referenced_table_name AS ftab,
209             kcu.referenced_column_name AS fcol
210             FROM information_schema.table_constraints tco
211             JOIN information_schema.key_column_usage kcu
212             ON tco.constraint_schema = kcu.constraint_schema
213             AND tco.constraint_name = kcu.constraint_name
214             AND tco.table_name = kcu.table_name
215             WHERE tco.table_schema NOT IN ('sys','information_schema', 'mysql', 'performance_schema')
216             AND tco.table_name = ?
217             AND tco.table_schema = ?
218             ORDER BY tco.table_schema, tco.table_name, tco.constraint_name, kcu.ordinal_position
219             EOT
220              
221 0           $sth->execute($table, $self->{+DB_NAME});
222              
223 0           my ($pk, %unique, @links);
224              
225 0           my %keys;
226 0           while (my $row = $sth->fetchrow_hashref) {
227 0   0       my $item = $keys{$row->{con}} //= {type => lc($row->{type})};
228              
229 0   0       push @{$item->{columns} //= []} => $row->{col};
  0            
230              
231 0 0         next unless $row->{type} eq 'FOREIGN KEY';
232              
233 0   0       my $link = $item->{link} //= [[$table, $item->{columns}],[$row->{ftab},[]]];
234 0           push @{$link->[1]->[1]} => $row->{fcol};
  0            
235             }
236              
237 0           for my $key (sort keys %keys) {
238 0           my $item = $keys{$key};
239              
240 0           my $type = delete $item->{type};
241 0 0 0       if ($type eq 'foreign key') {
    0          
    0          
242 0           push @links => $item->{link};
243             }
244             elsif ($type eq 'unique') {
245 0           $unique{column_key(@{$item->{columns}})} = $item->{columns};
  0            
246             }
247             elsif ($type eq 'unique' || $type eq 'primary key') {
248 0           $unique{column_key(@{$item->{columns}})} = $item->{columns};
  0            
249 0 0         $pk = $item->{columns} if $type eq 'primary key';
250             }
251             }
252              
253 0           $params{autofill}->hook(links => {links => \@links, table_name => $table});
254 0           $params{autofill}->hook(primary_key => {primary_key => $pk, table_name => $table});
255 0           $params{autofill}->hook(unique_keys => {unique_keys => \%unique, table_name => $table});
256              
257 0           return ($pk, \%unique, \@links);
258             }
259              
260             sub build_columns_from_db {
261 0     0 0   my $self = shift;
262 0           my ($table, %params) = @_;
263              
264 0 0         croak "A table name is required" unless $table;
265 0           my $dbh = $self->{+DBH};
266              
267 0           my $sth = $dbh->prepare(<<" EOT");
268             SELECT *
269             FROM information_schema.columns
270             WHERE table_name = ?
271             AND table_schema = ?
272             EOT
273              
274 0           $sth->execute($table, $self->{+DB_NAME});
275              
276 0           my (%columns, @links);
277 0           while (my $res = $sth->fetchrow_hashref) {
278 0           my $col = {};
279              
280 0           $params{autofill}->hook(pre_column => {column => $col, table_name => $table, column_info => $res});
281              
282 0           $col->{name} = $res->{COLUMN_NAME};
283 0           $col->{db_name} = $res->{COLUMN_NAME};
284 0           $col->{order} = $res->{ORDINAL_POSITION};
285 0           $col->{type} = \"$res->{DATA_TYPE}";
286 0           $col->{nullable} = $self->_col_field_to_bool($res->{IS_NULLABLE});
287              
288 0 0 0       $col->{identity} = 1 if $res->{EXTRA} && $res->{EXTRA} eq 'auto_increment';
289              
290 0   0       $col->{affinity} //= affinity_from_type($res->{DATA_TYPE});
291 0 0 0       $col->{affinity} //= 'string' if grep { $self->_col_field_to_bool($res->{$_}) } grep { m/CHARACTER/ } keys %$res;
  0            
  0            
292 0 0 0       $col->{affinity} //= 'numeric' if grep { $self->_col_field_to_bool($res->{$_}) } grep { m/NUMERIC/ } keys %$res;
  0            
  0            
293 0   0       $col->{affinity} //= 'string';
294              
295 0           $params{autofill}->process_column($col);
296 0           $params{autofill}->hook(post_column => {column => $col, table_name => $table, column_info => $res});
297              
298 0           $columns{$col->{name}} = DBIx::QuickORM::Schema::Table::Column->new($col);
299 0           $params{autofill}->hook(column => {column => $columns{$col->{name}}, table_name => $table, column_info => $res});
300             }
301              
302 0           return \%columns;
303             }
304              
305             sub _col_field_to_bool {
306 0     0     my $self = shift;
307 0           my ($val) = @_;
308              
309 0 0         return 0 unless defined $val;
310 0 0         return 0 unless $val;
311 0           $val = lc($val);
312 0 0         return 0 if $val eq 'no';
313 0 0         return 0 if $val eq 'undef';
314 0 0         return 0 if $val eq 'never';
315 0           return 1;
316             }
317              
318             sub build_indexes_from_db {
319 0     0 0   my $self = shift;
320 0           my ($table, %params) = @_;
321              
322 0           my $dbh = $self->dbh;
323              
324 0           my $sth = $dbh->prepare(<<" EOT");
325             SELECT index_name,
326             column_name,
327             non_unique,
328             index_type
329             FROM INFORMATION_SCHEMA.STATISTICS
330             WHERE table_name = ?
331             AND table_schema = ?
332             ORDER BY index_name, seq_in_index
333             EOT
334              
335 0           $sth->execute($table, $self->{+DB_NAME});
336              
337 0           my %out;
338 0           while (my ($name, $col, $nu, $type) = $sth->fetchrow_array) {
339 0 0 0       my $idx = $out{$name} //= {name => $name, unique => $nu ? 0 : 1, type => $type, columns => []};
340 0           push @{$idx->{columns}} => $col;
  0            
341             }
342              
343 0           return [map { $params{autofill}->hook(index => $out{$_}, table_name => $table); $out{$_} } sort keys %out];
  0            
  0            
344             }
345              
346             ###############################################################################
347             # }}} Schema Builder Code
348             ###############################################################################
349              
350             1;