File Coverage

blib/lib/Yancy/Backend/Mysql.pm
Criterion Covered Total %
statement 11 13 84.6
branch 1 2 50.0
condition n/a
subroutine 4 4 100.0
pod n/a
total 16 19 84.2


line stmt bran cond sub pod time code
1             package Yancy::Backend::Mysql;
2             our $VERSION = '1.088';
3             # ABSTRACT: A backend for MySQL using Mojo::mysql
4              
5             #pod =head1 SYNOPSIS
6             #pod
7             #pod ### URL string
8             #pod use Mojolicious::Lite;
9             #pod plugin Yancy => {
10             #pod backend => 'mysql:///mydb',
11             #pod read_schema => 1,
12             #pod };
13             #pod
14             #pod ### Mojo::mysql object
15             #pod use Mojolicious::Lite;
16             #pod use Mojo::mysql;
17             #pod plugin Yancy => {
18             #pod backend => { Mysql => Mojo::mysql->new( 'mysql:///mydb' ) },
19             #pod read_schema => 1,
20             #pod };
21             #pod
22             #pod ### Hash reference
23             #pod use Mojolicious::Lite;
24             #pod plugin Yancy => {
25             #pod backend => {
26             #pod Mysql => {
27             #pod dsn => 'dbi:mysql:dbname',
28             #pod username => 'fry',
29             #pod password => 'b3nd3r1sgr34t',
30             #pod },
31             #pod },
32             #pod read_schema => 1,
33             #pod };
34             #pod
35             #pod =head1 DESCRIPTION
36             #pod
37             #pod This Yancy backend allows you to connect to a MySQL database to manage
38             #pod the data inside. This backend uses L to connect to MySQL.
39             #pod
40             #pod See L for the methods this backend has and their return
41             #pod values.
42             #pod
43             #pod =head2 Backend URL
44             #pod
45             #pod The URL for this backend takes the form C<<
46             #pod mysql://:@:/ >>.
47             #pod
48             #pod Some examples:
49             #pod
50             #pod # Just a DB
51             #pod mysql:///mydb
52             #pod
53             #pod # User+DB (server on localhost:3306)
54             #pod mysql://user@/mydb
55             #pod
56             #pod # User+Pass Host and DB
57             #pod mysql://user:pass@example.com/mydb
58             #pod
59             #pod =head2 Schema Names
60             #pod
61             #pod The schema names for this backend are the names of the tables in the
62             #pod database.
63             #pod
64             #pod So, if you have the following schema:
65             #pod
66             #pod CREATE TABLE people (
67             #pod id INTEGER AUTO_INCREMENT PRIMARY KEY,
68             #pod name VARCHAR(255) NOT NULL,
69             #pod email VARCHAR(255) NOT NULL
70             #pod );
71             #pod CREATE TABLE business (
72             #pod id INTEGER AUTO_INCREMENT PRIMARY KEY,
73             #pod name VARCHAR(255) NOT NULL,
74             #pod email VARCHAR(255) NULL
75             #pod );
76             #pod
77             #pod You could map that to the following schema:
78             #pod
79             #pod {
80             #pod backend => 'mysql://user@/mydb',
81             #pod schema => {
82             #pod People => {
83             #pod required => [ 'name', 'email' ],
84             #pod properties => {
85             #pod id => {
86             #pod type => 'integer',
87             #pod readOnly => 1,
88             #pod },
89             #pod name => { type => 'string' },
90             #pod email => { type => 'string' },
91             #pod },
92             #pod },
93             #pod Business => {
94             #pod required => [ 'name' ],
95             #pod properties => {
96             #pod id => {
97             #pod type => 'integer',
98             #pod readOnly => 1,
99             #pod },
100             #pod name => { type => 'string' },
101             #pod email => { type => 'string' },
102             #pod },
103             #pod },
104             #pod },
105             #pod }
106             #pod
107             #pod =head1 SEE ALSO
108             #pod
109             #pod L, L
110             #pod
111             #pod =cut
112              
113 1     1   5895 use Mojo::Base 'Yancy::Backend::MojoDB';
  1         4  
  1         13  
114 1     1   310 use Scalar::Util qw( blessed );
  1         3  
  1         102  
115 1     1   6 use Yancy::Util qw( is_type is_format );
  1         2  
  1         159  
116             BEGIN {
117 1 50   1   4 eval { require Mojo::mysql; Mojo::mysql->VERSION( 1.05 ); 1 }
  1         240  
  0            
  0            
118             or die "Could not load Mysql backend: Mojo::mysql version 1.05 or higher required\n";
119             }
120              
121             sub new {
122             my ( $class, $driver, $schema ) = @_;
123             if ( blessed $driver ) {
124             die "Need a Mojo::mysql object. Got " . blessed( $driver )
125             if !$driver->isa( 'Mojo::mysql' );
126             return $class->SUPER::new( $driver, $schema );
127             }
128             elsif ( ref $driver eq 'HASH' ) {
129             return $class->SUPER::new( Mojo::mysql->new( $driver ), $schema );
130             }
131             my $found = (my $connect = $driver) =~ s{^.*?:}{};
132             return $class->SUPER::new(
133             Mojo::mysql->new( $found ? "mysql:$connect" : () ),
134             $schema,
135             );
136             }
137              
138             sub table_info {
139             my ( $self ) = @_;
140             my $dbh = $self->dbh;
141             my $schema = $self->driver->db->query( 'SELECT database()' )->array->[0];
142             return $dbh->table_info( undef, $schema, '%', undef )->fetchall_arrayref({});
143             }
144              
145             sub fixup_default {
146             my ( $self, $value ) = @_;
147             return undef if !defined $value;
148             return "now" if $value =~ /^(?:NOW|(?:CUR(?:RENT_)?|LOCAL|UTC_)(?:DATE|TIME(?:STAMP)?))(?:\(\))?/i;
149             $value;
150             }
151              
152             sub normalize {
153             my ( $self, $schema_name, $data ) = @_;
154             $data = $self->SUPER::normalize( $schema_name, $data ) || return undef;
155             my $schema = $self->schema->{ $schema_name };
156             my $real_schema_name = ( $schema->{'x-view'} || {} )->{schema} // $schema_name;
157             my %props = %{
158             $schema->{properties} || $self->schema->{ $real_schema_name }{properties}
159             };
160             my %replace;
161             for my $key ( keys %$data ) {
162             next if !defined $data->{ $key }; # leave nulls alone
163             my $prop = $props{ $key } || next;
164             my ( $type, $format ) = @{ $prop }{qw( type format )};
165             if ( is_type( $type, 'string' ) && is_format( $format, 'date-time' ) ) {
166             if ( !$data->{ $key } ) {
167             $replace{ $key } = undef;
168             }
169             elsif ( $data->{ $key } eq 'now' ) {
170             $replace{ $key } = \'NOW()';
171             }
172             }
173             }
174             return { %$data, %replace };
175             }
176              
177             sub column_info {
178             my ( $self, $table ) = @_;
179             my $columns = $self->dbh->column_info( @{$table}{qw( TABLE_CAT TABLE_SCHEM TABLE_NAME )}, '%' )->fetchall_arrayref({});
180             for my $c ( @$columns ) {
181             if ( $c->{mysql_values} ) {
182             $c->{ENUM} = $c->{mysql_values};
183             }
184             if ( $c->{mysql_is_auto_increment} ) {
185             $c->{AUTO_INCREMENT} = 1;
186             }
187             $c->{COLUMN_DEF} = $self->fixup_default( $c->{COLUMN_DEF} );
188             }
189             return $columns;
190             }
191              
192             1;
193              
194             __END__