File Coverage

blib/lib/Test/PONAPI/Repository/MockDB.pm
Criterion Covered Total %
statement 370 387 95.6
branch 90 120 75.0
condition 32 59 54.2
subroutine 38 38 100.0
pod 0 15 0.0
total 530 619 85.6


line stmt bran cond sub pod time code
1             # ABSTRACT: mock repository class
2             package Test::PONAPI::Repository::MockDB;
3              
4 8     8   7563 use Moose;
  8         23  
  8         60  
5              
6             # We MUST use DBD::SQLite before ::Constants to get anything useful!
7 8     8   64355 use DBD::SQLite;
  8         234904  
  8         346  
8 8     8   4027 use DBD::SQLite::Constants qw/:result_codes/;
  8         7369  
  8         2329  
9              
10 8     8   3812 use Test::PONAPI::Repository::MockDB::Loader;
  8         32  
  8         344  
11              
12 8     8   4490 use Test::PONAPI::Repository::MockDB::Table::Articles;
  8         37  
  8         327  
13 8     8   4791 use Test::PONAPI::Repository::MockDB::Table::People;
  8         37  
  8         311  
14 8     8   4175 use Test::PONAPI::Repository::MockDB::Table::Comments;
  8         33  
  8         327  
15              
16 8     8   71 use PONAPI::Constants;
  8         20  
  8         1146  
17 8     8   65 use PONAPI::Exception;
  8         20  
  8         35608  
18              
19             with 'PONAPI::Repository';
20              
21             has dbh => (
22             is => 'ro',
23             isa => 'DBI::db',
24             writer => '_set_dbh'
25             );
26              
27             has tables => (
28             is => 'ro',
29             isa => 'HashRef',
30             lazy => 1,
31             default => sub {
32             return +{
33             articles => Test::PONAPI::Repository::MockDB::Table::Articles->new,
34             people => Test::PONAPI::Repository::MockDB::Table::People->new,
35             comments => Test::PONAPI::Repository::MockDB::Table::Comments->new,
36             }
37             }
38             );
39              
40             sub BUILD {
41 13     13 0 46 my ($self, $params) = @_;
42 13         589 my $loader = Test::PONAPI::Repository::MockDB::Loader->new;
43 13 50       100 $loader->load unless $params->{skip_data_load};
44 13         108077 $self->_set_dbh( $loader->dbh );
45             }
46              
47             sub has_type {
48 257     257 0 1308 my ( $self, $type ) = @_;
49 257         8112 !! exists $self->tables->{$type};
50             }
51              
52             sub has_relationship {
53 117     117 0 355 my ( $self, $type, $rel_name ) = @_;
54 117 50       3531 if ( my $table = $self->tables->{$type} ) {
55 117         3983 my $relations = $table->RELATIONS;
56 117         1014 return !! exists $relations->{ $rel_name };
57             }
58 0         0 return 0;
59             }
60              
61             sub has_one_to_many_relationship {
62 160     160 0 433 my ( $self, $type, $rel_name ) = @_;
63 160 50       5436 if ( my $table = $self->tables->{$type} ) {
64 160         5281 my $relations = $table->RELATIONS;
65 160 50       480 return if !exists $relations->{ $rel_name };
66 160         6111 return !$relations->{ $rel_name }->ONE_TO_ONE;
67             }
68 0         0 return;
69             }
70              
71             sub type_has_fields {
72 56     56 0 154 my ($self, $type, $fields) = @_;
73              
74             # Check for invalid 'fields'
75 56         1689 my $table_obj = $self->tables->{$type};
76 56         128 my %columns = map +($_=>1), @{ $table_obj->COLUMNS };
  56         942  
77              
78 56 100       411 return 1 unless grep !exists $columns{$_}, @$fields;
79 10         57 return;
80             }
81              
82             sub retrieve_all {
83 54     54 0 327 my ( $self, %args ) = @_;
84 54         177 my $type = $args{type};
85              
86 54 100       184 $self->_validate_page($args{page}) if $args{page};
87              
88 54         1793 my $stmt = $self->tables->{$type}->select_stmt(%args);
89 54         388 $self->_add_resources( stmt => $stmt, %args );
90             }
91              
92             sub retrieve {
93 37     37 0 257 my ( $self, %args ) = @_;
94 37         153 $args{filter}{id} = delete $args{id};
95 37         233 $self->retrieve_all(%args);
96             }
97              
98             sub retrieve_relationships {
99 4     4 0 25 my ( $self, %args ) = @_;
100 4         60 my ($type, $rel_type, $doc) = @args{qw/type rel_type document/};
101              
102 4         11 my $page = $args{page};
103 4 100       26 $self->_validate_page($page) if $page;
104              
105 4   50     16 my $sort = $args{sort} || [];
106 4 100       16 if ( @$sort ) {
107 1 50 33     13 PONAPI::Exception->throw(
108             message => "You can only sort by id in retrieve_relationships"
109             ) if @$sort > 1 || $sort->[0] !~ /\A(-)?id\z/;
110              
111 1         4 my $desc = !!$1;
112              
113 1         36 my $table_obj = $self->tables->{$type};
114 1         37 my $relation_obj = $table_obj->RELATIONS->{$rel_type};
115 1         40 my $id_column = $relation_obj->REL_ID_COLUMN;
116              
117 1 50       8 @$sort = ($desc ? '-' : '') . $id_column;
118             }
119              
120 4         30 my $rels = $self->_find_resource_relationships(
121             %args,
122             # No need to fetch other relationship types
123             fields => { $type => [ $rel_type ] },
124             );
125              
126 4 50       15 return unless @{ $rels || [] };
  4 50       18  
127              
128 4         29 $doc->add_resource( %$_ ) for @$rels;
129              
130 4 100       4507 $self->_add_pagination_links(
131             page => $page,
132             document => $doc,
133             ) if $page;
134              
135             }
136              
137             sub retrieve_by_relationship {
138 6     6 0 40 my ( $self, %args ) = @_;
139 6         31 my ( $doc, $type, $rel_type, $fields, $include ) = @args{qw< document type rel_type fields include >};
140              
141 6   50     22 my $sort = delete $args{sort} || [];
142 6         16 my $page = delete $args{page};
143 6 100       34 $self->_validate_page($page) if $page;
144              
145             # We need to avoid passing sort and page here, since sort
146             # will have columns for the actual data, not the relationship
147             # table, and page needs to happen after sorting
148 6         53 my $rels = $self->_find_resource_relationships(
149             %args,
150             # No need to fetch other relationship types
151             fields => { $type => [ $rel_type ] },
152             );
153              
154 6 50       35 return unless @$rels;
155              
156 6         18 my $q_type = $rels->[0]{type};
157 6         15 my $q_ids = [ map { $_->{id} } @{$rels} ];
  10         24  
  6         18  
158              
159 6         252 my $stmt = $self->tables->{$q_type}->select_stmt(
160             type => $q_type,
161             fields => $fields,
162             filter => { id => $q_ids },
163             sort => $sort,
164             page => $page,
165             );
166              
167 6         33 $self->_add_resources(
168             document => $doc,
169             stmt => $stmt,
170             type => $q_type,
171             fields => $fields,
172             include => $include,
173             page => $page,
174             sort => $sort,
175             );
176             }
177              
178             sub create {
179 5     5 0 30 my ( $self, %args ) = @_;
180              
181 5         165 my $dbh = $self->dbh;
182 5         66 $dbh->begin_work;
183              
184 5         122 my ($e, $failed);
185             {
186 5         13 local $@;
  5         10  
187 5         28 eval { $self->_create( %args ); 1; }
  3         13  
188 5 100       14 or do {
189 2   50     14 ($failed, $e) = (1, $@||'Unknown error');
190             };
191             }
192 5 100       20 if ( $failed ) {
193 2         228 $dbh->rollback;
194 2         27 die $e;
195             }
196              
197 3         29978 $dbh->commit;
198              
199 3         53 return;
200             }
201              
202             sub _create {
203 5     5   26 my ( $self, %args ) = @_;
204 5         24 my ( $doc, $type, $data ) = @args{qw< document type data >};
205              
206 5   50     22 my $attributes = $data->{attributes} || {};
207 5   100     36 my $relationships = delete $data->{relationships} || {};
208              
209 5         172 my $table_obj = $self->tables->{$type};
210 5         48 my ($stmt, $return, $extra) = $table_obj->insert_stmt(
211             table => $type,
212             values => $attributes,
213             );
214              
215 5         22 $self->_db_execute( $stmt );
216              
217 4         169 my $new_id = $self->dbh->last_insert_id("","","","");
218              
219 4         19 foreach my $rel_type ( keys %$relationships ) {
220 2         9 my $rel_data = $relationships->{$rel_type};
221 2 50       13 $rel_data = [ $rel_data ] if ref($rel_data) ne 'ARRAY';
222 2         18 $self->_create_relationships(
223             %args,
224             id => $new_id,
225             rel_type => $rel_type,
226             data => $rel_data,
227             );
228             }
229              
230             # Spec says we MUST return this, both here and in the Location header;
231             # the DAO takes care of the header, but we need to put it in the doc
232 3         16 $doc->add_resource( type => $type, id => $new_id );
233              
234 3         2266 return;
235             }
236              
237             sub _create_relationships {
238 6     6   57 my ( $self, %args ) = @_;
239 6         38 my ( $type, $id, $rel_type, $data ) = @args{qw< type id rel_type data >};
240              
241 6         204 my $table_obj = $self->tables->{$type};
242 6         219 my $relation_obj = $table_obj->RELATIONS->{$rel_type};
243              
244 6         213 my $rel_table = $relation_obj->TABLE;
245 6         197 my $key_type = $relation_obj->TYPE;
246              
247 6         219 my $id_column = $relation_obj->ID_COLUMN;
248 6         234 my $rel_id_column = $relation_obj->REL_ID_COLUMN;
249              
250 6         16 my @all_values;
251 6         21 foreach my $orig ( @$data ) {
252 7         50 my $relationship = { %$orig };
253 7         61 my $data_type = delete $relationship->{type};
254              
255 7 100       38 if ( $data_type ne $key_type ) {
256 2         20 PONAPI::Exception->throw(
257             message => "Data has type `$data_type`, but we were expecting `$key_type`",
258             bad_request_data => 1,
259             );
260             }
261              
262 5         15 $relationship->{$id_column} = $id;
263 5         16 $relationship->{$rel_id_column} = delete $relationship->{id};
264              
265 5         56 push @all_values, $relationship;
266             }
267              
268 4         18 my $one_to_one = !$self->has_one_to_many_relationship($type, $rel_type);
269              
270 4         22 foreach my $values ( @all_values ) {
271 5         44 my ($stmt, $return, $extra) = $relation_obj->insert_stmt(
272             table => $rel_table,
273             values => $values,
274             );
275              
276 5         19 my ($failed, $e);
277             {
278 5         10 local $@;
  5         22  
279 5         19 eval { $self->_db_execute( $stmt ); 1; }
  4         37  
280 5 100       11 or do {
281 1   50     7 ($failed, $e) = (1, $@||'Unknown error');
282             };
283             }
284 5 100       28 if ( $failed ) {
285 1 50 33     7 if ( $one_to_one && do { local $@; eval { $e->sql_error } } ) {
  0         0  
  0         0  
  0         0  
286             # Can't quite do ::Upsert
287 0         0 $stmt = $relation_obj->update_stmt(
288             table => $rel_table,
289             values => [ %$values ],
290             where => { $id_column => $id },
291             driver => 'sqlite',
292             );
293 0         0 $self->_db_execute( $stmt );
294             }
295             else {
296 1         10 die $e;
297             }
298             };
299             }
300              
301 3         22 return PONAPI_UPDATED_NORMAL;
302             }
303              
304             sub create_relationships {
305 4     4 0 34 my ($self, %args) = @_;
306              
307 4         156 my $dbh = $self->dbh;
308 4         50 $dbh->begin_work;
309              
310 4         101 my ($ret, $e, $failed);
311             {
312 4         8 local $@;
  4         8  
313 4         43 eval { $ret = $self->_create_relationships( %args ); 1; }
  2         22  
314 4 100       11 or do {
315 2   50     8 ($failed, $e) = (1, $@||'Unknown error');
316             };
317             }
318 4 100       25 if ( $failed ) {
319 2         193 $dbh->rollback;
320 2         20 die $e;
321             }
322              
323 2         16754 $dbh->commit;
324 2         38 return $ret;
325             }
326              
327             sub update {
328 12     12 0 76 my ( $self, %args ) = @_;
329              
330 12         374 my $dbh = $self->dbh;
331 12         110 $dbh->begin_work;
332              
333 12         252 my ($ret, $e, $failed);
334             {
335 12         23 local $@;
  12         27  
336 12         112 eval { $ret = $self->_update( %args ); 1 }
  11         78  
337 12 100       28 or do {
338 1   50     6 ($failed, $e) = (1, $@||'Unknown error');
339             };
340             }
341 12 100       38 if ( $failed ) {
342 1         143 $dbh->rollback;
343 1         14 die $e;
344             }
345              
346 11         66909 $dbh->commit;
347 11         171 return $ret;
348             }
349              
350             sub _update {
351 12     12   62 my ( $self, %args ) = @_;
352 12         50 my ( $type, $id, $data ) = @args{qw< type id data >};
353 12   100     26 my ($attributes, $relationships) = map $_||{}, @{ $data }{qw/ attributes relationships /};
  12         92  
354              
355 12         39 my $return = PONAPI_UPDATED_NORMAL;
356 12 100       40 if ( %$attributes ) {
357 8         261 my $table_obj = $self->tables->{$type};
358             # Per the spec, the api behaves *very* differently if ->update does extra things
359             # under the hood. Case point: the updated column in Articles
360 8         289 my ($stmt, $extra_return, $msg) = $table_obj->update_stmt(
361             table => $type,
362             where => { $table_obj->ID_COLUMN => $id },
363             values => $attributes,
364             );
365              
366 8 100       28 $return = $extra_return if defined $extra_return;
367              
368 8         41 my $sth = $self->_db_execute( $stmt );
369              
370             # We had a successful update, but it updated nothing
371 8 100       123 if ( !$sth->rows ) {
372 3         42 $return = PONAPI_UPDATED_NOTHING;
373             }
374             }
375              
376 12         54 foreach my $rel_type ( keys %$relationships ) {
377             my $update_rel_return = $self->_update_relationships(
378             type => $type,
379             id => $id,
380             rel_type => $rel_type,
381 9         51 data => $relationships->{$rel_type},
382             );
383              
384             # We tried updating the attributes but
385 8 50 33     162 $return = $update_rel_return
386             if $return == PONAPI_UPDATED_NOTHING
387             && $update_rel_return != PONAPI_UPDATED_NOTHING;
388             }
389              
390 11         61 return $return;
391             }
392              
393             sub _update_relationships {
394 13     13   78 my ($self, %args) = @_;
395 13         73 my ( $type, $id, $rel_type, $data ) = @args{qw< type id rel_type data >};
396              
397 13         479 my $table_obj = $self->tables->{$type};
398 13         463 my $relation_obj = $table_obj->RELATIONS->{$rel_type};
399              
400 13         450 my $column_rel_type = $relation_obj->TYPE;
401 13         428 my $rel_table = $relation_obj->TABLE;
402              
403 13         480 my $id_column = $relation_obj->ID_COLUMN;
404 13         504 my $rel_id_column = $relation_obj->REL_ID_COLUMN;
405              
406             # Let's have an arrayref
407 13 50       115 $data = $data
    100          
    100          
408             ? ref($data) eq 'HASH' ? [ keys(%$data) ? $data : () ] : $data
409             : [];
410              
411             # Let's start by clearing all relationships; this way
412             # we can implement the SQL below without adding special cases
413             # for ON DUPLICATE KEY UPDATE and sosuch.
414 13         84 my $stmt = $relation_obj->delete_stmt(
415             table => $rel_table,
416             where => { $id_column => $id },
417             );
418 13         70 $self->_db_execute( $stmt );
419              
420 13         64 my $return = PONAPI_UPDATED_NORMAL;
421 13         48 foreach my $insert ( @$data ) {
422             my ($stmt, $insert_return, $extra) = $table_obj->insert_stmt(
423             table => $rel_table,
424             values => {
425             $id_column => $id,
426             $rel_id_column => $insert->{id},
427             },
428 9         78 );
429 9         40 $self->_db_execute( $stmt );
430              
431 8 50       43 $return = $insert_return if $insert_return;
432             }
433              
434 12         84 return $return;
435             }
436              
437             sub update_relationships {
438 4     4 0 25 my ( $self, %args ) = @_;
439              
440 4         117 my $dbh = $self->dbh;
441 4         44 $dbh->begin_work;
442              
443 4         98 my ($ret, $e, $failed);
444             {
445 4         7 local $@;
  4         15  
446 4         27 eval { $ret = $self->_update_relationships( %args ); 1 }
  4         22  
447 4 50       12 or do {
448 0   0     0 ($failed, $e) = (1, $@||'Unknown error');
449             };
450             }
451 4 50       23 if ( $failed ) {
452 0         0 $dbh->rollback;
453 0         0 die $e;
454             }
455              
456 4         27799 $dbh->commit;
457              
458 4         66 return $ret;
459             }
460              
461             sub delete : method {
462 3     3 0 18 my ( $self, %args ) = @_;
463 3         11 my ( $type, $id ) = @args{qw< type id >};
464              
465 3         98 my $table_obj = $self->tables->{$type};
466 3         34 my $stmt = $table_obj->delete_stmt(
467             table => $type,
468             where => { id => $id },
469             );
470              
471 3         23 my $sth = $self->_db_execute( $stmt );
472              
473 3         82 return;
474             }
475              
476             sub delete_relationships {
477 4     4 0 33 my ( $self, %args ) = @_;
478              
479 4         141 my $dbh = $self->dbh;
480 4         54 $dbh->begin_work;
481              
482 4         102 my ($ret, $e, $failed);
483             {
484 4         9 local $@;
  4         10  
485 4         27 eval { $ret = $self->_delete_relationships( %args ); 1 }
  4         30  
486 4 50       9 or do {
487 0   0     0 ($failed, $e) = (1, $@||'Unknown error');
488             };
489             }
490 4 50       14 if ( $failed ) {
491 0         0 $dbh->rollback;
492 0         0 die $e;
493             }
494              
495 4         22400 $dbh->commit;
496              
497 4         93 return $ret;
498             }
499              
500             sub _delete_relationships {
501 4     4   26 my ( $self, %args ) = @_;
502 4         21 my ( $type, $id, $rel_type, $data ) = @args{qw< type id rel_type data >};
503              
504 4         131 my $table_obj = $self->tables->{$type};
505 4         135 my $relation_obj = $table_obj->RELATIONS->{$rel_type};
506              
507 4         136 my $table = $relation_obj->TABLE;
508 4         130 my $key_type = $relation_obj->TYPE;
509              
510 4         133 my $id_column = $relation_obj->ID_COLUMN;
511 4         235 my $rel_id_column = $relation_obj->REL_ID_COLUMN;
512              
513 4         11 my @all_values;
514 4         15 foreach my $resource ( @$data ) {
515 4         12 my $data_type = $resource->{type};
516              
517 4 50       18 if ( $data_type ne $key_type ) {
518 0         0 PONAPI::Exception->throw(
519             message => "Data has type `$data_type`, but we were expecting `$key_type`",
520             bad_request_data => 1,
521             );
522             }
523              
524             my $delete_where = {
525             $id_column => $id,
526             $rel_id_column => $resource->{id},
527 4         21 };
528              
529 4         15 push @all_values, $delete_where;
530             }
531              
532 4         18 my $ret = PONAPI_UPDATED_NORMAL;
533              
534 4         11 my $rows_modified = 0;
535             DELETE:
536 4         11 foreach my $where ( @all_values ) {
537 4         32 my $stmt = $relation_obj->delete_stmt(
538             table => $table,
539             where => $where,
540             );
541              
542 4         17 my $sth = $self->_db_execute( $stmt );
543 4         85 $rows_modified += $sth->rows;
544             }
545              
546 4 100       22 $ret = PONAPI_UPDATED_NOTHING if !$rows_modified;
547              
548 4         28 return $ret;
549             }
550              
551              
552             ## --------------------------------------------------------
553              
554             sub _add_resources {
555 60     60   386 my ( $self, %args ) = @_;
556             my ( $doc, $stmt, $type ) =
557 60         226 @args{qw< document stmt type >};
558              
559 60         225 my $sth = $self->_db_execute( $stmt );
560              
561 60         2318 while ( my $row = $sth->fetchrow_hashref() ) {
562 78         346 my $id = delete $row->{id};
563 78         478 my $rec = $doc->add_resource( type => $type, id => $id );
564 78         63585 $rec->add_attribute( $_ => $row->{$_} ) for keys %{$row};
  78         593  
565 78         49499 $rec->add_self_link;
566              
567 78         53620 $self->_add_resource_relationships($rec, %args);
568             }
569              
570             $self->_add_pagination_links(
571             page => $args{page},
572             rows => scalar $sth->rows,
573             document => $doc,
574 60 100       380 ) if $args{page};
575              
576 60         27492 return;
577             }
578              
579             sub _add_pagination_links {
580 7     7   38 my ($self, %args) = @_;
581 7         30 my ($page, $rows_fetched, $document) = @args{qw/page rows document/};
582 7   100     28 $rows_fetched ||= -1;
583              
584 7         18 my ($offset, $limit) = @{$page}{qw/offset limit/};
  7         23  
585              
586 7         41 my %current = %$page;
587 7         32 my %first = ( %current, offset => 0, );
588 7         21 my (%previous, %next);
589              
590 7 100       30 if ( ($offset - $limit) >= 0 ) {
591 4         13 %previous = %current;
592 4         13 $previous{offset} -= $current{limit};
593             }
594              
595 7 100       25 if ( $rows_fetched >= $limit ) {
596 6         20 %next = %current;
597 6         18 $next{offset} += $limit;
598             }
599              
600             $document->add_pagination_links(
601 7         60 first => \%first,
602             self => \%current,
603             prev => \%previous,
604             next => \%next,
605             );
606             }
607              
608             sub _validate_page {
609 7     7   19 my ($self, $page) = @_;
610              
611             exists $page->{limit}
612 7 50       28 or PONAPI::Exception->throw(message => "Limit missing for `page`");
613              
614 7 50       50 $page->{limit} =~ /\A[0-9]+\z/
615             or PONAPI::Exception->throw(message => "Bad limit value ($page->{limit}) in `page`");
616              
617 7 50 66     51 !exists $page->{offset} || ($page->{offset} =~ /\A[0-9]+\z/)
618             or PONAPI::Exception->throw(message => "Bad offset value in `page`");
619              
620 7   100     32 $page->{offset} ||= 0;
621              
622 7         14 return;
623             }
624              
625             sub _add_resource_relationships {
626 78     78   649 my ( $self, $rec, %args ) = @_;
627 78         313 my $doc = $rec->find_root;
628 78         11244 my $type = $rec->type;
629 78         711 my $fields = $args{fields};
630 78         193 my %include = map { $_ => 1 } @{ $args{include} };
  30         143  
  78         256  
631              
632             # Do not add sort or page here -- those were for the primary resource
633             # *only*.
634 78         2326 my $rels = $self->_fetchall_relationships(
635             type => $type,
636             id => $rec->id,
637             document => $doc,
638             fields => $fields,
639             );
640 78 50       267 $rels or return;
641              
642 78         319 for my $r ( keys %$rels ) {
643 122         8785 my $relationship = $rels->{$r};
644 122 100       331 @$relationship or next;
645              
646 98         232 my $rel_type = $relationship->[0]{type};
647              
648             # skipping the relationship if the type has an empty `fields` set
649 98 100 100     344 next if exists $fields->{$rel_type} and !@{ $fields->{$rel_type} };
  6         25  
650              
651 96         329 my $one_to_many = $self->has_one_to_many_relationship($type, $r);
652 96         268 for ( @$relationship ) {
653 119         121499 $rec->add_relationship( $r, $_, $one_to_many )
654             ->add_self_link
655             ->add_related_link;
656             }
657              
658             $self->_add_included(
659             $rel_type, # included type
660 19         140 +[ map { $_->{id} } @$relationship ], # included ids
661             %args # filters / fields / etc.
662 96 100       483767 ) if exists $include{$r};
663             }
664              
665 78         9170 return;
666             }
667              
668             sub _add_included {
669 16     16   108 my ( $self, $type, $ids, %args ) = @_;
670 16         62 my ( $doc, $filter, $fields ) = @args{qw< document filter fields >};
671              
672 16         50 $filter->{id} = $ids;
673              
674             # Do NOT add sort -- sort here was for the *main* resource!
675 16         540 my $stmt = $self->tables->{$type}->select_stmt(
676             type => $type,
677             filter => $filter,
678             fields => $fields,
679             );
680              
681 16         63 my $sth = $self->_db_execute( $stmt );
682              
683 16         459 while ( my $inc = $sth->fetchrow_hashref() ) {
684 18         2768 my $id = delete $inc->{id};
685             $doc->add_included( type => $type, id => $id )
686 18         146 ->add_attributes( %{$inc} )
  18         12299  
687             ->add_self_link;
688             }
689             }
690              
691             sub _find_resource_relationships {
692 10     10   64 my ( $self, %args ) = @_;
693 10         35 my $rel_type = $args{rel_type};
694              
695 10 50 33     64 if ( $rel_type and my $rels = $self->_fetchall_relationships(%args) ) {
696 10 50       71 return $rels->{$rel_type} if exists $rels->{$rel_type};
697             }
698              
699 0         0 return [];
700             }
701              
702             sub _fetchall_relationships {
703 88     88   1135 my ( $self, %args ) = @_;
704 88         321 my ( $type, $id ) = @args{qw< type id >};
705              
706             # we don't want to autovivify $args{fields}{$type}
707             # since it will be checked in order to know whether
708             # the key existed in the original fields argument
709             my %type_fields = exists $args{fields}{$type}
710 88 100       484 ? map { $_ => 1 } @{ $args{fields}{$type} }
  23         81  
  20         64  
711             : ();
712              
713 88         182 my %ret;
714             my @errors;
715              
716 88         151 for my $name ( keys %{ $self->tables->{$type}->RELATIONS } ) {
  88         2793  
717             # If we have fields, and this relationship is not mentioned, skip
718             # it.
719 157 100 100     6546 next if keys %type_fields > 0 and !exists $type_fields{$name};
720              
721 132         4512 my $table_obj = $self->tables->{$type};
722 132         4379 my $rel_table_obj = $table_obj->RELATIONS->{$name};
723 132         4234 my $rel_type = $rel_table_obj->TYPE;
724 132         4270 my $rel_table = $rel_table_obj->TABLE;
725 132         4302 my $id_column = $rel_table_obj->ID_COLUMN;
726 132         5118 my $rel_id_column = $rel_table_obj->REL_ID_COLUMN;
727              
728 132         923 my $stmt = $rel_table_obj->select_stmt(
729             %args,
730             type => $rel_table,
731             filter => { $id_column => $id },
732             fields => [ $rel_id_column ],
733             );
734              
735 132         567 my $sth = $self->_db_execute( $stmt );
736              
737             $ret{$name} = +[
738             map +{ type => $rel_type, id => $_->{$rel_id_column} },
739 132         280 @{ $sth->fetchall_arrayref({}) }
  132         1308  
740             ];
741             }
742              
743 88         7661 return \%ret;
744             }
745              
746             # Might not be there?
747             my $sqlite_constraint_failed = do {
748             local $@;
749             eval { SQLITE_CONSTRAINT() } // undef;
750             };
751             sub _db_execute {
752 255     255   619 my ( $self, $stmt ) = @_;
753              
754 255         460 my ($sth, $ret, $failed, $e);
755             {
756 255         401 local $@;
  255         430  
757             eval {
758 255         8588 $sth = $self->dbh->prepare($stmt->{sql});
759 255 50       21571 $ret = $sth->execute(@{ $stmt->{bind} || [] });
  255         38544  
760             # This should never happen, since the DB handle is
761             # created with RaiseError.
762 252 50       1207 die $DBI::errstr if !$ret;
763 252         1004 1;
764 255 100       489 } or do {
765 3         50 $failed = 1;
766 3   50     12 $e = $@ || 'Unknown error';
767             };
768             };
769 255 100       635 if ( $failed ) {
770 3   50     54 my $errstr = $DBI::errstr || "Unknown SQL error";
771 3   50     24 my $err_id = $DBI::err || 0;
772              
773 3         6 my $message;
774 3 50 33     33 if ( $sqlite_constraint_failed && $err_id && $err_id == $sqlite_constraint_failed ) {
    0 33        
775 3         27 PONAPI::Exception->throw(
776             message => "Table constraint failed: $errstr",
777             sql_error => 1,
778             status => 409,
779             );
780             }
781             elsif ( $err_id ) {
782 0         0 PONAPI::Exception->throw(
783             message => $errstr,
784             sql_error => 1,
785             );
786             }
787             else {
788 0         0 PONAPI::Exception->throw(
789             message => "Non-SQL error while running query? $e"
790             )
791             }
792             };
793              
794 252         945 return $sth;
795             }
796              
797             __PACKAGE__->meta->make_immutable;
798 8     8   78 no Moose; 1;
  8         22  
  8         57  
799              
800             __END__
801              
802             =pod
803              
804             =encoding UTF-8
805              
806             =head1 NAME
807              
808             Test::PONAPI::Repository::MockDB - mock repository class
809              
810             =head1 VERSION
811              
812             version 0.003003
813              
814             =head1 AUTHORS
815              
816             =over 4
817              
818             =item *
819              
820             Mickey Nasriachi <mickey@cpan.org>
821              
822             =item *
823              
824             Stevan Little <stevan@cpan.org>
825              
826             =item *
827              
828             Brian Fraser <hugmeir@cpan.org>
829              
830             =back
831              
832             =head1 COPYRIGHT AND LICENSE
833              
834             This software is copyright (c) 2019 by Mickey Nasriachi, Stevan Little, Brian Fraser.
835              
836             This is free software; you can redistribute it and/or modify it under
837             the same terms as the Perl 5 programming language system itself.
838              
839             =cut