File Coverage

blib/lib/Test/PONAPI/Repository/MockDB.pm
Criterion Covered Total %
statement 372 389 95.6
branch 89 118 75.4
condition 32 59 54.2
subroutine 39 39 100.0
pod 0 15 0.0
total 532 620 85.8


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