File Coverage

blib/lib/DBIx/QuickORM/Dialect/PostgreSQL.pm
Criterion Covered Total %
statement 33 173 19.0
branch 0 34 0.0
condition 0 49 0.0
subroutine 11 36 30.5
pod 0 24 0.0
total 44 316 13.9


line stmt bran cond sub pod time code
1             package DBIx::QuickORM::Dialect::PostgreSQL;
2 1     1   7 use strict;
  1         3  
  1         47  
3 1     1   6 use warnings;
  1         2  
  1         107  
4              
5             our $VERSION = '0.000019';
6              
7 1     1   7 use Carp qw/croak/;
  1         2  
  1         77  
8 1     1   5 use DBIx::QuickORM::Util qw/column_key/;
  1         3  
  1         11  
9 1     1   49 use DBIx::QuickORM::Affinity qw/affinity_from_type/;
  1         2  
  1         67  
10              
11 1     1   886 use DBIx::QuickORM::Schema;
  1         4  
  1         43  
12 1     1   914 use DBIx::QuickORM::Schema::Table;
  1         5  
  1         47  
13 1     1   914 use DBIx::QuickORM::Schema::Table::Column;
  1         5  
  1         40  
14 1     1   860 use DBIx::QuickORM::Schema::View;
  1         4  
  1         40  
15              
16 1     1   8 use parent 'DBIx::QuickORM::Dialect';
  1         3  
  1         5  
17 1     1   105 use DBIx::QuickORM::Util::HashBase;
  1         3  
  1         7  
18              
19 0     0 0   sub dbi_driver { 'DBD::Pg' }
20 0     0 0   sub dialect_name { 'PostgreSQL' }
21              
22 0     0 0   sub quote_binary_data { { pg_type => DBD::Pg::PG_BYTEA() } }
23              
24 0     0 0   sub supports_returning_update { 1 }
25 0     0 0   sub supports_returning_insert { 1 }
26 0     0 0   sub supports_returning_delete { 1 }
27              
28 0     0 0   sub async_supported { 1 }
29 0     0 0   sub async_cancel_supported { 1 }
30 0     0 0   sub async_prepare_args { pg_async => DBD::Pg::PG_ASYNC() }
31 0     0 0   sub async_result { my ($s, %p) = @_; $p{sth}->pg_result() }
  0            
32 0   0 0 0   sub async_ready { my ($s, %p) = @_; my $dbh = $p{dbh} // $s->dbh; $dbh->pg_ready() }
  0            
  0            
33 0   0 0 0   sub async_cancel { my ($s, %p) = @_; my $dbh = $p{dbh} // $s->dbh; $dbh->pg_cancel() }
  0            
  0            
34              
35 0   0 0 0   sub start_txn { my ($s, %p) = @_; my $dbh = $p{dbh} // $s->dbh; $dbh->begin_work }
  0            
  0            
36 0   0 0 0   sub commit_txn { my ($s, %p) = @_; my $dbh = $p{dbh} // $s->dbh; $dbh->commit }
  0            
  0            
37 0   0 0 0   sub rollback_txn { my ($s, %p) = @_; my $dbh = $p{dbh} // $s->dbh; $dbh->rollback }
  0            
  0            
38 0   0 0 0   sub create_savepoint { my ($s, %p) = @_; my $dbh = $p{dbh} // $s->dbh; $dbh->pg_savepoint($p{savepoint}) }
  0            
  0            
39 0   0 0 0   sub commit_savepoint { my ($s, %p) = @_; my $dbh = $p{dbh} // $s->dbh; $dbh->pg_release($p{savepoint}) }
  0            
  0            
40 0   0 0 0   sub rollback_savepoint { my ($s, %p) = @_; my $dbh = $p{dbh} // $s->dbh; $dbh->pg_rollback_to($p{savepoint}) }
  0            
  0            
41              
42             my %TYPES = (
43             uuid => 'UUID',
44             );
45             sub supports_type {
46 0     0 0   my $self = shift;
47 0           my ($type) = @_;
48 0           return $TYPES{lc($type)};
49             }
50              
51             sub db_version {
52 0     0 0   my $self = shift;
53              
54 0           my $dbh = $self->{+DBH};
55              
56 0           my $sth = $dbh->prepare("SHOW server_version");
57 0           $sth->execute();
58              
59 0           my ($ver) = $sth->fetchrow_array;
60 0           return $ver;
61             }
62              
63             ###############################################################################
64             # {{{ Schema Builder Code
65             ###############################################################################
66              
67             my %TABLE_TYPES = (
68             'BASE TABLE' => 'DBIx::QuickORM::Schema::Table',
69             'VIEW' => 'DBIx::QuickORM::Schema::View',
70             'LOCAL TEMPORARY' => 'DBIx::QuickORM::Schema::Table',
71             );
72              
73             my %TEMP_TYPES = (
74             'BASE TABLE' => 0,
75             'VIEW' => 0,
76             'LOCAL TEMPORARY' => 1,
77             );
78              
79             sub build_tables_from_db {
80 0     0 0   my $self = shift;
81 0           my %params = @_;
82              
83 0           my $dbh = $self->{+DBH};
84              
85 0           my $sth = $dbh->prepare(<<" EOT");
86             SELECT table_name, table_type
87             FROM information_schema.tables
88             WHERE table_catalog = ?
89             AND table_schema NOT IN ('pg_catalog', 'information_schema')
90             EOT
91              
92 0           $sth->execute($self->{+DB_NAME});
93              
94 0           my %tables;
95              
96 0           while (my ($tname, $type) = $sth->fetchrow_array) {
97 0 0         next if $params{autofill}->skip(table => $tname);
98              
99 0   0       my $table = {name => $tname, db_name => $tname, is_temp => $TEMP_TYPES{$type} // 0};
100 0   0       my $class = $TABLE_TYPES{$type} // 'DBIx::QuickORM::Schema::Table';
101              
102 0           $params{autofill}->hook(pre_table => {table => $table, class => \$class});
103              
104 0           $table->{columns} = $self->build_columns_from_db($tname, %params);
105 0           $params{autofill}->hook(columns => {columns => $table->{columns}, table => $table});
106              
107 0           $table->{indexes} = $self->build_indexes_from_db($tname, %params);
108 0           $params{autofill}->hook(indexes => {indexes => $table->{indexes}, table => $table});
109              
110 0           @{$table}{qw/primary_key unique _links/} = $self->build_table_keys_from_db($tname, %params);
  0            
111              
112 0           $params{autofill}->hook(post_table => {table => $table, class => \$class});
113              
114 0           $tables{$table->{name}} = $class->new($table);
115 0           $params{autofill}->hook(table => {table => $tables{$tname}});
116             }
117              
118 0           return \%tables;
119             }
120              
121             sub build_table_keys_from_db {
122 0     0 0   my $self = shift;
123 0           my ($table, %params) = @_;
124              
125 0           my $dbh = $self->{+DBH};
126              
127 0           my $sth = $dbh->prepare(<<" EOT");
128             SELECT pg_get_constraintdef(oid)
129             FROM pg_constraint
130             WHERE connamespace = 'public'::regnamespace AND conrelid::regclass::text = ?
131             EOT
132              
133 0           $sth->execute($table);
134              
135 0           my ($pk, %unique, @links);
136              
137 0           while (my ($spec) = $sth->fetchrow_array) {
138 0 0         if (my ($type, $columns) = $spec =~ m/^(UNIQUE|PRIMARY KEY) \(([^\)]+)\)$/gi) {
139 0           my @columns = split /,\s+/, $columns;
140              
141 0 0         $pk = \@columns if $type eq 'PRIMARY KEY';
142              
143 0           my $key = column_key(@columns);
144 0           $unique{$key} = \@columns;
145             }
146              
147 0 0         if (my ($type, $columns, $ftable, $fcolumns) = $spec =~ m/(FOREIGN KEY) \(([^\)]+)\) REFERENCES\s+(\S+)\(([^\)]+)\)/gi) {
148 0           my @columns = split /,\s+/, $columns;
149 0           my @fcolumns = split /,\s+/, $fcolumns;
150              
151 0           push @links => [[$table, \@columns], [$ftable, \@fcolumns]];
152             }
153             }
154              
155 0           $params{autofill}->hook(links => {links => \@links, table_name => $table});
156 0           $params{autofill}->hook(primary_key => {primary_key => $pk, table_name => $table});
157 0           $params{autofill}->hook(unique_keys => {unique_keys => \%unique, table_name => $table});
158              
159 0           return ($pk, \%unique, \@links);
160             }
161              
162             sub build_columns_from_db {
163 0     0 0   my $self = shift;
164 0           my ($table, %params) = @_;
165              
166 0 0         croak "A table name is required" unless $table;
167 0           my $dbh = $self->{+DBH};
168              
169 0           my $sth = $dbh->prepare(<<" EOT");
170             SELECT *
171             FROM information_schema.columns
172             WHERE table_catalog = ?
173             AND table_name = ?
174             AND table_schema NOT IN ('pg_catalog', 'information_schema')
175             EOT
176              
177 0           $sth->execute($self->{+DB_NAME}, $table);
178              
179 0           my (%columns, @links);
180 0           while (my $res = $sth->fetchrow_hashref) {
181 0 0         next if $params{autofill}->skip(column => ($table, $res->{column_name}));
182              
183 0           my $col = {};
184              
185 0           $params{autofill}->hook(pre_column => {column => $col, table_name => $table, column_info => $res});
186              
187 0           $col->{name} = $res->{column_name};
188 0           $col->{db_name} = $res->{column_name};
189 0           $col->{order} = $res->{ordinal_position};
190 0           $col->{type} = \"$res->{udt_name}";
191 0           $col->{nullable} = $self->_col_field_to_bool($res->{is_nullable});
192              
193 0 0 0       $col->{identity} //= 1 if grep { $self->_col_field_to_bool($res->{$_}) } grep { m/identity/ } keys %$res;
  0            
  0            
194 0 0 0       $col->{identity} //= 1 if $res->{column_default} && $res->{column_default} =~ m/^nextval\(/;
      0        
195              
196 0   0       $col->{affinity} //= affinity_from_type($res->{udt_name}) // affinity_from_type($res->{data_type});
      0        
197 0 0 0       $col->{affinity} //= 'string' if grep { $self->_col_field_to_bool($res->{$_}) } grep { m/character/ } keys %$res;
  0            
  0            
198 0 0 0       $col->{affinity} //= 'numeric' if grep { $self->_col_field_to_bool($res->{$_}) } grep { m/numeric/ } keys %$res;
  0            
  0            
199 0   0       $col->{affinity} //= 'string';
200              
201 0           $params{autofill}->process_column($col);
202              
203 0           $params{autofill}->hook(post_column => {column => $col, table_name => $table, column_info => $res});
204              
205 0           $columns{$col->{name}} = DBIx::QuickORM::Schema::Table::Column->new($col);
206 0           $params{autofill}->hook(column => {column => $columns{$col->{name}}, table_name => $table, column_info => $res});
207             }
208              
209 0           return \%columns;
210             }
211              
212             sub _col_field_to_bool {
213 0     0     my $self = shift;
214 0           my ($val) = @_;
215              
216 0 0         return 0 unless defined $val;
217 0 0         return 0 unless $val;
218 0           $val = lc($val);
219 0 0         return 0 if $val eq 'no';
220 0 0         return 0 if $val eq 'undef';
221 0 0         return 0 if $val eq 'never';
222 0           return 1;
223             }
224              
225             sub build_indexes_from_db {
226 0     0 0   my $self = shift;
227 0           my ($table, %params) = @_;
228              
229 0           my $dbh = $self->dbh;
230              
231 0           my $sth = $dbh->prepare(<<" EOT");
232             SELECT indexname AS name,
233             indexdef AS def
234             FROM pg_indexes
235             WHERE tablename = ?
236             ORDER BY name
237             EOT
238              
239 0           $sth->execute($table);
240              
241 0           my @out;
242              
243 0           while (my ($name, $def) = $sth->fetchrow_array) {
244 0 0 0       $def =~ m/CREATE(?: (UNIQUE))? INDEX \Q$name\E ON \S+ USING ([^\(]+) \((.+)\)$/ or warn "Could not parse index: $def" and next;
245 0           my ($unique, $type, $col_list) = ($1, $2, $3);
246 0           my @cols = split /,\s*/, $col_list;
247 0 0         push @out => {name => $name, type => $type, columns => \@cols, unique => $unique ? 1 : 0};
248 0           $params{autofill}->hook(index => {index => $out[-1], table_name => $table, definition => $def});
249             }
250              
251 0           return \@out;
252             }
253              
254             ###############################################################################
255             # }}} Schema Builder Code
256             ###############################################################################
257              
258              
259             1;