File Coverage

blib/lib/Test/PONAPI/Repository/MockDB/Table.pm
Criterion Covered Total %
statement 71 71 100.0
branch 20 22 90.9
condition 6 7 85.7
subroutine 9 9 100.0
pod 0 4 0.0
total 106 113 93.8


line stmt bran cond sub pod time code
1             # ABSTRACT: mock repository - table class
2             package Test::PONAPI::Repository::MockDB::Table;
3              
4 8     8   4594 use Moose;
  8         28  
  8         50  
5              
6 8     8   50930 use Test::PONAPI::Repository::MockDB::Table::Relationships;
  8         27  
  8         9263  
7              
8             has [qw/TYPE TABLE ID_COLUMN/] => (
9             is => 'ro',
10             isa => 'Str',
11             required => 1,
12             );
13              
14             has COLUMNS => (
15             is => 'ro',
16             isa => 'ArrayRef',
17             required => 1,
18             );
19              
20             has RELATIONS => (
21             is => 'ro',
22             isa => 'HashRef[Test::PONAPI::Repository::MockDB::Table::Relationships]',
23             default => sub { {} },
24             );
25              
26             sub insert_stmt {
27 19     19 0 92 my ($self, %args) = @_;
28              
29 19         48 my $table = $args{table};
30 19         46 my $values = $args{values};
31              
32             # NOTE: this is a bunch of bad practices rolled together.
33             # We're crafting our own SQL and not escaping the table/columns,
34             # as well as using sqlite-specific features.
35             # Ordinarily, you'd use DBIx::Class or at least SQL::Composer
36             # for this, but we got reports that packaging PONAPI::Server
37             # becomes hugely complex by adding either of those as dependencies.
38             # Since this is just for testing, let's forgo a couple of good practices
39             # and do it all manually.
40 19         69 my @keys = keys %$values;
41 19         228 my @values = values %$values;
42 19 100       170 my $sql = "INSERT INTO $table " . (@keys
43             ? '(' . join( ",", @keys) . ') VALUES (' . join(',', ('?') x @keys) . ')'
44             : 'DEFAULT VALUES');
45              
46 19         76 my $stmt = {
47             sql => $sql,
48             bind => \@values,
49             };
50              
51 19         99 return $stmt;
52             }
53              
54             sub delete_stmt {
55 20     20 0 125 my ($self, %args) = @_;
56              
57 20         61 my $table = $args{table};
58 20         50 my $where = $args{where};
59              
60 20         72 my @keys = keys %$where;
61 20         85 my @values = values %$where;
62              
63 20         156 my $sql = "DELETE FROM $table WHERE "
64             . join " AND ", map "$_=?", @keys;
65              
66 20         80 my $stmt = { sql => $sql, bind => \@values };
67              
68 20         86 return $stmt;
69             }
70              
71             sub select_stmt {
72 208     208 0 1151 my ($self, %args) = @_;
73              
74 208         480 my $type = $args{type};
75 208         695 my $filters = $self->_stmt_filters($type, $args{filter});
76              
77 208 100       411 my %limit = %{ $args{page} || {} };
  208         1142  
78 208   100     916 my $sort = $args{sort} || [];
79              
80             my @order_by = map {
81 208         488 my ($desc, $col) = /\A(-?)(.+)\z/s;
  8         47  
82 8 50       59 join ' ', $col => uc( $desc ? 'desc' : 'asc' );
83             } @$sort;
84              
85 208         740 my $columns = $self->_stmt_columns(\%args);
86 208 100       699 my @values = map { ref($_) ? @$_ : $_ } values %$filters;
  193         860  
87             my $sql = join "\n",
88             'SELECT ' . join(',', @$columns),
89             'FROM ' . $type,
90             (%$filters
91             ? 'WHERE ' . join(' AND ', map {
92 208 100       1349 my $val = $filters->{$_};
  193 100       436  
    100          
93 193 100       1271 ref($val)
94 24         227 ? "$_ IN (@{[ join ',', ('?') x @$val ]})"
95             : "$_=?"
96             } keys %$filters)
97             : ''
98             ),
99             (@order_by ? 'ORDER BY ' . join(', ', @order_by) : ''),
100             (%limit ? "LIMIT $limit{limit} OFFSET $limit{offset}" : '' );
101              
102 208         833 my $stmt = {
103             sql => $sql,
104             bind => \@values,
105             };
106              
107 208         1120 return $stmt;
108             }
109              
110             sub update_stmt {
111 8     8 0 32 my ($self, %args) = @_;
112              
113 8         17 my $id = $args{id};
114 8         28 my $table = $args{table};
115 8   50     24 my $values = $args{values} || {};
116 8         15 my $where = $args{where};
117              
118 8         36 my @cols = keys %$values;
119 8         24 my @values = values %$values;
120 8         23 push @values, values %$where;
121              
122 8         75 my $sql = join "\n",
123             "UPDATE $table",
124             "SET " . join(', ', map "$_=?", @cols),
125             "WHERE " . join( ' AND ', map "$_=?", keys %$where );
126              
127 8         37 my $stmt = {
128             sql => $sql,
129             bind => \@values,
130             };
131              
132 8         42 return $stmt;
133             }
134              
135             sub _stmt_columns {
136 208     208   385 my $self = shift;
137 208         358 my $args = shift;
138 208         374 my ( $fields, $type ) = @{$args}{qw< fields type >};
  208         563  
139              
140 208         498 my $ref = ref $fields;
141              
142 208 100       5149 return [ $self->ID_COLUMN, @$fields ] if $ref eq 'ARRAY';
143              
144 76 100 100     1253 $ref eq 'HASH' and exists $fields->{$type}
145             or return $self->COLUMNS;
146              
147             my @fields_minus_relationship_keys =
148 12         440 grep { !exists $self->RELATIONS->{$_} }
149 10         19 @{ $fields->{$type} };
  10         23  
150              
151 10         374 return +[ $self->ID_COLUMN, @fields_minus_relationship_keys ];
152             }
153              
154             sub _stmt_filters {
155 208     208   489 my ( $self, $type, $filter ) = @_;
156              
157 208 50       6825 return $filter if $self->TABLE ne $type;
158              
159             return +{
160 193         952 map { $_ => $filter->{$_} }
161 640         1664 grep { exists $filter->{$_} }
162 208         401 @{ $self->COLUMNS }
  208         5467  
163             };
164             }
165              
166             __PACKAGE__->meta->make_immutable;
167 8     8   72 no Moose; 1;
  8         24  
  8         43  
168              
169             __END__
170              
171             =pod
172              
173             =encoding UTF-8
174              
175             =head1 NAME
176              
177             Test::PONAPI::Repository::MockDB::Table - mock repository - table class
178              
179             =head1 VERSION
180              
181             version 0.003003
182              
183             =head1 AUTHORS
184              
185             =over 4
186              
187             =item *
188              
189             Mickey Nasriachi <mickey@cpan.org>
190              
191             =item *
192              
193             Stevan Little <stevan@cpan.org>
194              
195             =item *
196              
197             Brian Fraser <hugmeir@cpan.org>
198              
199             =back
200              
201             =head1 COPYRIGHT AND LICENSE
202              
203             This software is copyright (c) 2019 by Mickey Nasriachi, Stevan Little, Brian Fraser.
204              
205             This is free software; you can redistribute it and/or modify it under
206             the same terms as the Perl 5 programming language system itself.
207              
208             =cut