File Coverage

blib/lib/Yancy/Backend/Pg.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::Pg;
2             our $VERSION = '1.088';
3             # ABSTRACT: A backend for Postgres using Mojo::Pg
4              
5             #pod =head1 SYNOPSIS
6             #pod
7             #pod ### URL string
8             #pod use Mojolicious::Lite;
9             #pod plugin Yancy => {
10             #pod backend => 'pg://user:pass@localhost/mydb',
11             #pod read_schema => 1,
12             #pod };
13             #pod
14             #pod ### Mojo::Pg object
15             #pod use Mojolicious::Lite;
16             #pod use Mojo::Pg;
17             #pod plugin Yancy => {
18             #pod backend => { Pg => Mojo::Pg->new( 'postgres:///myapp' ) },
19             #pod read_schema => 1,
20             #pod };
21             #pod
22             #pod ### Hashref
23             #pod use Mojolicious::Lite;
24             #pod plugin Yancy => {
25             #pod backend => {
26             #pod Pg => {
27             #pod dsn => 'dbi:Pg:dbname',
28             #pod username => 'fry',
29             #pod password => 'b3nd3r1sgr34t',
30             #pod },
31             #pod },
32             #pod read_schema => 1,
33             #pod };
34             #pod
35             #pod
36             #pod =head1 DESCRIPTION
37             #pod
38             #pod This Yancy backend allows you to connect to a Postgres database to manage
39             #pod the data inside. This backend uses L to connect to Postgres.
40             #pod
41             #pod See L for the methods this backend has and their return
42             #pod values.
43             #pod
44             #pod =head2 Backend URL
45             #pod
46             #pod The URL for this backend takes the form C<<
47             #pod pg://:@:/ >>.
48             #pod
49             #pod Some examples:
50             #pod
51             #pod # Just a DB
52             #pod pg:///mydb
53             #pod
54             #pod # User+DB (server on localhost:5432)
55             #pod pg://user@/mydb
56             #pod
57             #pod # User+Pass Host and DB
58             #pod pg://user:pass@example.com/mydb
59             #pod
60             #pod =head2 Schema Names
61             #pod
62             #pod The schema names for this backend are the names of the tables in the
63             #pod database.
64             #pod
65             #pod So, if you have the following schema:
66             #pod
67             #pod CREATE TABLE people (
68             #pod id SERIAL,
69             #pod name VARCHAR NOT NULL,
70             #pod email VARCHAR NOT NULL
71             #pod );
72             #pod CREATE TABLE business (
73             #pod id SERIAL,
74             #pod name VARCHAR NOT NULL,
75             #pod email VARCHAR NULL
76             #pod );
77             #pod
78             #pod You could map that to the following schema:
79             #pod
80             #pod {
81             #pod backend => 'pg://user@/mydb',
82             #pod schema => {
83             #pod People => {
84             #pod required => [ 'name', 'email' ],
85             #pod properties => {
86             #pod id => {
87             #pod type => 'integer',
88             #pod readOnly => 1,
89             #pod },
90             #pod name => { type => 'string' },
91             #pod email => { type => 'string' },
92             #pod },
93             #pod },
94             #pod Business => {
95             #pod required => [ 'name' ],
96             #pod properties => {
97             #pod id => {
98             #pod type => 'integer',
99             #pod readOnly => 1,
100             #pod },
101             #pod name => { type => 'string' },
102             #pod email => { type => 'string' },
103             #pod },
104             #pod },
105             #pod },
106             #pod }
107             #pod
108             #pod =head2 Ignored Tables
109             #pod
110             #pod By default, this backend will ignore some tables when using
111             #pod C: Tables used by L,
112             #pod L (in case we're co-habitating with
113             #pod a DBIx::Class schema), and all the tables used by the
114             #pod L Minion backend.
115             #pod
116             #pod =head1 SEE ALSO
117             #pod
118             #pod L, L
119             #pod
120             #pod =cut
121              
122 1     1   6413 use Mojo::Base 'Yancy::Backend::MojoDB';
  1         3  
  1         13  
123 1     1   292 use Mojo::JSON qw( encode_json );
  1         3  
  1         77  
124 1     1   7 use Scalar::Util qw( blessed );
  1         3  
  1         132  
125             BEGIN {
126 1 50   1   4 eval { require Mojo::Pg; Mojo::Pg->VERSION( 4.03 ); 1 }
  1         286  
  0            
  0            
127             or die "Could not load Pg backend: Mojo::Pg version 4.03 or higher required\n";
128             }
129              
130             sub new {
131             my ( $class, $driver, $schema ) = @_;
132             if ( blessed $driver ) {
133             die "Need a Mojo::Pg object. Got " . blessed( $driver )
134             if !$driver->isa( 'Mojo::Pg' );
135             return $class->SUPER::new( $driver, $schema );
136             }
137             elsif ( ref $driver eq 'HASH' ) {
138             my $pg = Mojo::Pg->new;
139             for my $method ( keys %$driver ) {
140             $pg->$method( $driver->{ $method } );
141             }
142             return $class->SUPER::new( $pg, $schema );
143             }
144             my $found = (my $connect = $driver) =~ s{^.*?:}{};
145             return $class->SUPER::new(
146             Mojo::Pg->new( $found ? "postgres:$connect" : () ),
147             $schema,
148             );
149             }
150              
151             sub table_info {
152             my ( $self ) = @_;
153             my $dbh = $self->dbh;
154             my $schema = $self->driver->db->query( 'SELECT current_schema()' )->array->[0];
155             return $dbh->table_info( undef, $schema, '%', undef )->fetchall_arrayref({});
156             }
157              
158             sub fixup_default {
159             my ( $self, $value ) = @_;
160             return undef if !defined $value or $value =~ /^nextval/i or $value eq 'NULL';
161             return "now" if $value =~ /^(?:NOW|(?:CURRENT_|LOCAL|STATEMENT_|TRANSACTION_|CLOCK_)(?:DATE|TIME(?:STAMP)?))(?:\(\))?/i;
162             return 0 if $value eq '0.0';
163             $self->driver->db->query( 'SELECT ' . $value )->array->[0];
164             }
165              
166             sub create {
167             my ( $self, $coll, $params ) = @_;
168             $params = $self->normalize( $coll, $params );
169             my $id_field = $self->id_field( $coll );
170             my $res = $self->driver->db->insert( $coll, $params, { returning => $id_field } );
171             my $row = $res->hash;
172             return ref $id_field eq 'ARRAY'
173             ? { map { $_ => $row->{$_} } @$id_field }
174             : $row->{ $id_field }
175             ;
176             }
177              
178             sub create_p {
179             my ( $self, $coll, $params ) = @_;
180             $params = $self->normalize( $coll, $params );
181             my $id_field = $self->id_field( $coll );
182             return $self->driver->db->insert_p( $coll, $params, { returning => $id_field } )
183             ->then( sub {
184             my $row = shift->hash;
185             return ref $id_field eq 'ARRAY'
186             ? { map { $_ => $row->{$_} } @$id_field }
187             : $row->{ $id_field }
188             ;
189             } );
190             }
191              
192             sub column_info {
193             my ( $self, $table ) = @_;
194             my $columns = $self->dbh->column_info( @{$table}{qw( TABLE_CAT TABLE_SCHEM TABLE_NAME )}, '%' )->fetchall_arrayref({});
195             for my $c ( @$columns ) {
196             if ( $c->{pg_enum_values} ) {
197             $c->{ENUM} = $c->{pg_enum_values};
198             }
199             if ( $c->{COLUMN_DEF} && $c->{COLUMN_DEF} =~ /nextval/i ) {
200             $c->{AUTO_INCREMENT} = 1;
201             }
202             $c->{COLUMN_DEF} = $self->fixup_default( $c->{COLUMN_DEF} );
203             }
204             return $columns;
205             }
206              
207             1;
208              
209             __END__