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   9816 use Moose;
  8         21  
  8         71  
5              
6 8     8   3086873 use SQL::Composer;
  8         8453740  
  8         523  
7              
8             # We MUST use DBD::SQLite before ::Constants to get anything useful!
9 8     8   10268 use DBD::SQLite;
  8         3943432  
  8         317  
10 8     8   6639 use DBD::SQLite::Constants qw/:result_codes/;
  8         5506  
  8         3212  
11              
12 8     8   5620 use Test::PONAPI::Repository::MockDB::Loader;
  8         28  
  8         338  
13              
14 8     8   6456 use Test::PONAPI::Repository::MockDB::Table::Articles;
  8         34  
  8         333  
15 8     8   6746 use Test::PONAPI::Repository::MockDB::Table::People;
  8         32  
  8         323  
16 8     8   6351 use Test::PONAPI::Repository::MockDB::Table::Comments;
  8         30  
  8         318  
17              
18 8     8   70 use PONAPI::Constants;
  8         16  
  8         1167  
19 8     8   50 use PONAPI::Exception;
  8         20  
  8         37680  
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 35 my ($self, $params) = @_;
44 13         687 my $loader = Test::PONAPI::Repository::MockDB::Loader->new;
45 13 50       104 $loader->load unless $params->{skip_data_load};
46 13         525004 $self->_set_dbh( $loader->dbh );
47             }
48              
49             sub has_type {
50 257     257 0 1151 my ( $self, $type ) = @_;
51 257         10985 !! exists $self->tables->{$type};
52             }
53              
54             sub has_relationship {
55 88     88 0 212 my ( $self, $type, $rel_name ) = @_;
56 88 50       3531 if ( my $table = $self->tables->{$type} ) {
57 88         4140 my $relations = $table->RELATIONS;
58 88         1139 return !! exists $relations->{ $rel_name };
59             }
60 0         0 return 0;
61             }
62              
63             sub has_one_to_many_relationship {
64 160     160 0 401 my ( $self, $type, $rel_name ) = @_;
65 160 50       6984 if ( my $table = $self->tables->{$type} ) {
66 160         7246 my $relations = $table->RELATIONS;
67 160 50       617 return if !exists $relations->{ $rel_name };
68 160         8352 return !$relations->{ $rel_name }->ONE_TO_ONE;
69             }
70 0         0 return;
71             }
72              
73             sub type_has_fields {
74 56     56 0 122 my ($self, $type, $fields) = @_;
75              
76             # Check for invalid 'fields'
77 56         2325 my $table_obj = $self->tables->{$type};
78 56         111 my %columns = map +($_=>1), @{ $table_obj->COLUMNS };
  56         1273  
79              
80 56 100       544 return 1 unless grep !exists $columns{$_}, @$fields;
81 10         57 return;
82             }
83              
84             sub retrieve_all {
85 54     54 0 337 my ( $self, %args ) = @_;
86 54         172 my $type = $args{type};
87              
88 54 100       223 $self->_validate_page($args{page}) if $args{page};
89              
90 54         2599 my $stmt = $self->tables->{$type}->select_stmt(%args);
91 54         406 $self->_add_resources( stmt => $stmt, %args );
92             }
93              
94             sub retrieve {
95 37     37 0 300 my ( $self, %args ) = @_;
96 37         162 $args{filter}{id} = delete $args{id};
97 37         242 $self->retrieve_all(%args);
98             }
99              
100             sub retrieve_relationships {
101 4     4 0 28 my ( $self, %args ) = @_;
102 4         14 my ($type, $rel_type, $doc) = @args{qw/type rel_type document/};
103              
104 4         10 my $page = $args{page};
105 4 100       19 $self->_validate_page($page) if $page;
106              
107 4   50     13 my $sort = $args{sort} || [];
108 4 100       13 if ( @$sort ) {
109 1 50 33     15 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         3 my $desc = !!$1;
114              
115 1         42 my $table_obj = $self->tables->{$type};
116 1         43 my $relation_obj = $table_obj->RELATIONS->{$rel_type};
117 1         53 my $id_column = $relation_obj->REL_ID_COLUMN;
118              
119 1 50       8 @$sort = ($desc ? '-' : '') . $id_column;
120             }
121              
122 4         33 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       23  
129              
130 4         34 $doc->add_resource( %$_ ) for @$rels;
131              
132 4 100       36 $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 40 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     23 my $sort = delete $args{sort} || [];
144 6         16 my $page = delete $args{page};
145 6 100       18 $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         51 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       39 return unless @$rels;
157              
158 6         17 my $q_type = $rels->[0]{type};
159 6         13 my $q_ids = [ map { $_->{id} } @{$rels} ];
  10         28  
  6         13  
160              
161 6         280 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         38 $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 31 my ( $self, %args ) = @_;
182              
183 5         4077 my $dbh = $self->dbh;
184 5         110 $dbh->begin_work;
185              
186 5         133 my ($e, $failed);
187             {
188 5         11 local $@;
  5         10  
189 5         41 eval { $self->_create( %args ); 1; }
  3         17  
190 5 100       12 or do {
191 2   50     11 ($failed, $e) = (1, $@||'Unknown error');
192             };
193             }
194 5 100       21 if ( $failed ) {
195 2         216 $dbh->rollback;
196 2         25 die $e;
197             }
198              
199 3         264988 $dbh->commit;
200              
201 3         75 return;
202             }
203              
204             sub _create {
205 5     5   30 my ( $self, %args ) = @_;
206 5         19 my ( $doc, $type, $data ) = @args{qw< document type data >};
207              
208 5   50     24 my $attributes = $data->{attributes} || {};
209 5   100     30 my $relationships = delete $data->{relationships} || {};
210              
211 5         245 my $table_obj = $self->tables->{$type};
212 5         50 my ($stmt, $return, $extra) = $table_obj->insert_stmt(
213             table => $type,
214             values => $attributes,
215             );
216              
217 5         24 $self->_db_execute( $stmt );
218              
219 4         197 my $new_id = $self->dbh->last_insert_id("","","","");
220              
221 4         19 foreach my $rel_type ( keys %$relationships ) {
222 2         6 my $rel_data = $relationships->{$rel_type};
223 2 50       13 $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         19 $doc->add_resource( type => $type, id => $new_id );
235              
236 3         23 return;
237             }
238              
239             sub _create_relationships {
240 6     6   41 my ( $self, %args ) = @_;
241 6         27 my ( $type, $id, $rel_type, $data ) = @args{qw< type id rel_type data >};
242              
243 6         256 my $table_obj = $self->tables->{$type};
244 6         261 my $relation_obj = $table_obj->RELATIONS->{$rel_type};
245              
246 6         263 my $rel_table = $relation_obj->TABLE;
247 6         253 my $key_type = $relation_obj->TYPE;
248              
249 6         266 my $id_column = $relation_obj->ID_COLUMN;
250 6         295 my $rel_id_column = $relation_obj->REL_ID_COLUMN;
251              
252 6         13 my @all_values;
253 6         15 foreach my $orig ( @$data ) {
254 7         25 my $relationship = { %$orig };
255 7         20 my $data_type = delete $relationship->{type};
256              
257 7 100       27 if ( $data_type ne $key_type ) {
258 2         16 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         15 $relationship->{$id_column} = $id;
265 5         13 $relationship->{$rel_id_column} = delete $relationship->{id};
266              
267 5         14 push @all_values, $relationship;
268             }
269              
270 4         15 my $one_to_one = !$self->has_one_to_many_relationship($type, $rel_type);
271              
272 4         14 foreach my $values ( @all_values ) {
273 5         49 my ($stmt, $return, $extra) = $relation_obj->insert_stmt(
274             table => $rel_table,
275             values => $values,
276             );
277              
278 5         11 my ($failed, $e);
279             {
280 5         9 local $@;
  5         9  
281 5         16 eval { $self->_db_execute( $stmt ); 1; }
  4         20  
282 5 100       12 or do {
283 1   50     8 ($failed, $e) = (1, $@||'Unknown error');
284             };
285             }
286 5 100       56 if ( $failed ) {
287 1 50 33     20 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         16 die $e;
299             }
300             };
301             }
302              
303 3         24 return PONAPI_UPDATED_NORMAL;
304             }
305              
306             sub create_relationships {
307 4     4 0 29 my ($self, %args) = @_;
308              
309 4         160 my $dbh = $self->dbh;
310 4         59 $dbh->begin_work;
311              
312 4         106 my ($ret, $e, $failed);
313             {
314 4         9 local $@;
  4         8  
315 4         29 eval { $ret = $self->_create_relationships( %args ); 1; }
  2         11  
316 4 100       11 or do {
317 2   50     11 ($failed, $e) = (1, $@||'Unknown error');
318             };
319             }
320 4 100       21 if ( $failed ) {
321 2         205 $dbh->rollback;
322 2         23 die $e;
323             }
324              
325 2         128301 $dbh->commit;
326 2         80 return $ret;
327             }
328              
329             sub update {
330 12     12 0 78 my ( $self, %args ) = @_;
331              
332 12         528 my $dbh = $self->dbh;
333 12         156 $dbh->begin_work;
334              
335 12         340 my ($ret, $e, $failed);
336             {
337 12         25 local $@;
  12         22  
338 12         76 eval { $ret = $self->_update( %args ); 1 }
  11         55  
339 12 100       32 or do {
340 1   50     6 ($failed, $e) = (1, $@||'Unknown error');
341             };
342             }
343 12 100       40 if ( $failed ) {
344 1         156 $dbh->rollback;
345 1         11 die $e;
346             }
347              
348 11         2459858 $dbh->commit;
349 11         297 return $ret;
350             }
351              
352             sub _update {
353 12     12   72 my ( $self, %args ) = @_;
354 12         50 my ( $type, $id, $data ) = @args{qw< type id data >};
355 12   100     24 my ($attributes, $relationships) = map $_||{}, @{ $data }{qw/ attributes relationships /};
  12         111  
356              
357 12         38 my $return = PONAPI_UPDATED_NORMAL;
358 12 100       42 if ( %$attributes ) {
359 8         388 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         70 my ($stmt, $extra_return, $msg) = $table_obj->update_stmt(
363             table => $type,
364             id => $id,
365             values => $attributes,
366             );
367              
368 8 100       31 $return = $extra_return if defined $extra_return;
369              
370 8         36 my $sth = $self->_db_execute( $stmt );
371              
372             # We had a successful update, but it updated nothing
373 8 100       169 if ( !$sth->rows ) {
374 3         53 $return = PONAPI_UPDATED_NOTHING;
375             }
376             }
377              
378 12         50 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         39 data => $relationships->{$rel_type},
384             );
385              
386             # We tried updating the attributes but
387 8 50 33     34 $return = $update_rel_return
388             if $return == PONAPI_UPDATED_NOTHING
389             && $update_rel_return != PONAPI_UPDATED_NOTHING;
390             }
391              
392 11         69 return $return;
393             }
394              
395             sub _update_relationships {
396 13     13   69 my ($self, %args) = @_;
397 13         41 my ( $type, $id, $rel_type, $data ) = @args{qw< type id rel_type data >};
398              
399 13         571 my $table_obj = $self->tables->{$type};
400 13         589 my $relation_obj = $table_obj->RELATIONS->{$rel_type};
401              
402 13         709 my $column_rel_type = $relation_obj->TYPE;
403 13         581 my $rel_table = $relation_obj->TABLE;
404              
405 13         588 my $id_column = $relation_obj->ID_COLUMN;
406 13         695 my $rel_id_column = $relation_obj->REL_ID_COLUMN;
407              
408             # Let's have an arrayref
409 13 50       77 $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         64 $self->_db_execute( $stmt );
421              
422 13         33 my $return = PONAPI_UPDATED_NORMAL;
423 13         39 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         83 );
431 9         33 $self->_db_execute( $stmt );
432              
433 8 50       104 $return = $insert_return if $insert_return;
434             }
435              
436 12         108 return $return;
437             }
438              
439             sub update_relationships {
440 4     4 0 43 my ( $self, %args ) = @_;
441              
442 4         165 my $dbh = $self->dbh;
443 4         67 $dbh->begin_work;
444              
445 4         108 my ($ret, $e, $failed);
446             {
447 4         7 local $@;
  4         8  
448 4         26 eval { $ret = $self->_update_relationships( %args ); 1 }
  4         21  
449 4 50       9 or do {
450 0   0     0 ($failed, $e) = (1, $@||'Unknown error');
451             };
452             }
453 4 50       17 if ( $failed ) {
454 0         0 $dbh->rollback;
455 0         0 die $e;
456             }
457              
458 4         692905 $dbh->commit;
459              
460 4         123 return $ret;
461             }
462              
463             sub delete : method {
464 3     3 0 18 my ( $self, %args ) = @_;
465 3         9 my ( $type, $id ) = @args{qw< type id >};
466              
467 3         118 my $table_obj = $self->tables->{$type};
468 3         40 my $stmt = $table_obj->delete_stmt(
469             table => $type,
470             where => { id => $id },
471             );
472              
473 3         19 my $sth = $self->_db_execute( $stmt );
474              
475 3         170 return;
476             }
477              
478             sub delete_relationships {
479 4     4 0 33 my ( $self, %args ) = @_;
480              
481 4         170 my $dbh = $self->dbh;
482 4         60 $dbh->begin_work;
483              
484 4         107 my ($ret, $e, $failed);
485             {
486 4         8 local $@;
  4         8  
487 4         30 eval { $ret = $self->_delete_relationships( %args ); 1 }
  4         27  
488 4 50       12 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         225419 $dbh->commit;
498              
499 4         83 return $ret;
500             }
501              
502             sub _delete_relationships {
503 4     4   29 my ( $self, %args ) = @_;
504 4         17 my ( $type, $id, $rel_type, $data ) = @args{qw< type id rel_type data >};
505              
506 4         211 my $table_obj = $self->tables->{$type};
507 4         184 my $relation_obj = $table_obj->RELATIONS->{$rel_type};
508              
509 4         225 my $table = $relation_obj->TABLE;
510 4         174 my $key_type = $relation_obj->TYPE;
511              
512 4         182 my $id_column = $relation_obj->ID_COLUMN;
513 4         228 my $rel_id_column = $relation_obj->REL_ID_COLUMN;
514              
515 4         11 my @all_values;
516 4         12 foreach my $resource ( @$data ) {
517 4         19 my $data_type = $resource->{type};
518              
519 4 50       19 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         30 };
530              
531 4         21 push @all_values, $delete_where;
532             }
533              
534 4         10 my $ret = PONAPI_UPDATED_NORMAL;
535              
536 4         10 my $rows_modified = 0;
537             DELETE:
538 4         9 foreach my $where ( @all_values ) {
539 4         35 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         111 $rows_modified += $sth->rows;
546             }
547              
548 4 100       44 $ret = PONAPI_UPDATED_NOTHING if !$rows_modified;
549              
550 4         31 return $ret;
551             }
552              
553              
554             ## --------------------------------------------------------
555              
556             sub _add_resources {
557 60     60   401 my ( $self, %args ) = @_;
558             my ( $doc, $stmt, $type ) =
559 60         233 @args{qw< document stmt type >};
560              
561 60         236 my $sth = $self->_db_execute( $stmt );
562              
563 60         2374 while ( my $row = $sth->fetchrow_hashref() ) {
564 78         248 my $id = delete $row->{id};
565 78         497 my $rec = $doc->add_resource( type => $type, id => $id );
566 78         145 $rec->add_attribute( $_ => $row->{$_} ) for keys %{$row};
  78         671  
567 78         457 $rec->add_self_link;
568              
569 78         571 $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       427 ) if $args{page};
577              
578 60         1872 return;
579             }
580              
581             sub _add_pagination_links {
582 7     7   33 my ($self, %args) = @_;
583 7         24 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         22  
587              
588 7         37 my %current = %$page;
589 7         33 my %first = ( %current, offset => 0, );
590 7         13 my (%previous, %next);
591              
592 7 100       99 if ( ($offset - $limit) >= 0 ) {
593 4         16 %previous = %current;
594 4         12 $previous{offset} -= $current{limit};
595             }
596              
597 7 100       22 if ( $rows_fetched >= $limit ) {
598 6         19 %next = %current;
599 6         18 $next{offset} += $limit;
600             }
601              
602             $document->add_pagination_links(
603 7         60 first => \%first,
604             self => \%current,
605             prev => \%previous,
606             next => \%next,
607             );
608             }
609              
610             sub _validate_page {
611 7     7   18 my ($self, $page) = @_;
612              
613             exists $page->{limit}
614 7 50       31 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     28 $page->{offset} ||= 0;
623              
624 7         13 return;
625             }
626              
627             sub _add_resource_relationships {
628 78     78   641 my ( $self, $rec, %args ) = @_;
629 78         345 my $doc = $rec->find_root;
630 78         3203 my $type = $rec->type;
631 78         192 my $fields = $args{fields};
632 78         186 my %include = map { $_ => 1 } @{ $args{include} };
  30         180  
  78         244  
633              
634             # Do not add sort or page here -- those were for the primary resource
635             # *only*.
636 78         3241 my $rels = $self->_fetchall_relationships(
637             type => $type,
638             id => $rec->id,
639             document => $doc,
640             fields => $fields,
641             );
642 78 50       298 $rels or return;
643              
644 78         280 for my $r ( keys %$rels ) {
645 122         287 my $relationship = $rels->{$r};
646 122 100       396 @$relationship or next;
647              
648 98         224 my $rel_type = $relationship->[0]{type};
649              
650             # skipping the relationship if the type has an empty `fields` set
651 98 100 100     389 next if exists $fields->{$rel_type} and !@{ $fields->{$rel_type} };
  6         32  
652              
653 96         402 my $one_to_many = $self->has_one_to_many_relationship($type, $r);
654 96         305 for ( @$relationship ) {
655 119         601 $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         198 +[ map { $_->{id} } @$relationship ], # included ids
663             %args # filters / fields / etc.
664 96 100       408 ) if exists $include{$r};
665             }
666              
667 78         3173 return;
668             }
669              
670             sub _add_included {
671 16     16   142 my ( $self, $type, $ids, %args ) = @_;
672 16         57 my ( $doc, $filter, $fields ) = @args{qw< document filter fields >};
673              
674 16         93 $filter->{id} = $ids;
675              
676             # Do NOT add sort -- sort here was for the *main* resource!
677 16         746 my $stmt = $self->tables->{$type}->select_stmt(
678             type => $type,
679             filter => $filter,
680             fields => $fields,
681             );
682              
683 16         73 my $sth = $self->_db_execute( $stmt );
684              
685 16         528 while ( my $inc = $sth->fetchrow_hashref() ) {
686 18         52 my $id = delete $inc->{id};
687             $doc->add_included( type => $type, id => $id )
688 18         118 ->add_attributes( %{$inc} )
  18         126  
689             ->add_self_link;
690             }
691             }
692              
693             sub _find_resource_relationships {
694 10     10   57 my ( $self, %args ) = @_;
695 10         26 my $rel_type = $args{rel_type};
696              
697 10 50 33     78 if ( $rel_type and my $rels = $self->_fetchall_relationships(%args) ) {
698 10 50       67 return $rels->{$rel_type} if exists $rels->{$rel_type};
699             }
700              
701 0         0 return [];
702             }
703              
704             sub _fetchall_relationships {
705 88     88   448 my ( $self, %args ) = @_;
706 88         276 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       333 ? map { $_ => 1 } @{ $args{fields}{$type} }
  23         82  
  20         66  
713             : ();
714              
715 88         185 my %ret;
716             my @errors;
717              
718 88         155 for my $name ( keys %{ $self->tables->{$type}->RELATIONS } ) {
  88         3719  
719             # If we have fields, and this relationship is not mentioned, skip
720             # it.
721 157 100 100     6034 next if keys %type_fields > 0 and !exists $type_fields{$name};
722              
723 132         5780 my $table_obj = $self->tables->{$type};
724 132         6230 my $rel_table_obj = $table_obj->RELATIONS->{$name};
725 132         6096 my $rel_type = $rel_table_obj->TYPE;
726 132         5804 my $rel_table = $rel_table_obj->TABLE;
727 132         5992 my $id_column = $rel_table_obj->ID_COLUMN;
728 132         7033 my $rel_id_column = $rel_table_obj->REL_ID_COLUMN;
729              
730 132         1145 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         614 my $sth = $self->_db_execute( $stmt );
738              
739             $ret{$name} = +[
740             map +{ type => $rel_type, id => $_->{$rel_id_column} },
741 132         288 @{ $sth->fetchall_arrayref({}) }
  132         1226  
742             ];
743             }
744              
745 88         7274 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   526 my ( $self, $stmt ) = @_;
755              
756 255         388 my ($sth, $ret, $failed, $e);
757             {
758 255         361 local $@;
  255         398  
759             eval {
760 255         12160 $sth = $self->dbh->prepare($stmt->to_sql);
761 255         30938 $ret = $sth->execute($stmt->to_bind);
762             # This should never happen, since the DB handle is
763             # created with RaiseError.
764 252 50       149008 die $DBI::errstr if !$ret;
765 252         1099 1;
766 255 100       525 } or do {
767 3         326 $failed = 1;
768 3   50     13 $e = $@ || 'Unknown error';
769             };
770             };
771 255 100       685 if ( $failed ) {
772 3   50     52 my $errstr = $DBI::errstr || "Unknown SQL error";
773 3   50     24 my $err_id = $DBI::err || 0;
774              
775 3         8 my $message;
776 3 50 33     34 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         991 return $sth;
797             }
798              
799             __PACKAGE__->meta->make_immutable;
800 8     8   69 no Moose; 1;
  8         19  
  8         72  
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.002005
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