File Coverage

blib/lib/Metabase/Archive/SQL.pm
Criterion Covered Total %
statement 120 125 96.0
branch 6 12 50.0
condition n/a
subroutine 33 35 94.2
pod 0 5 0.0
total 159 177 89.8


line stmt bran cond sub pod time code
1 3     3   1892 use 5.006;
  3         9  
2 3     3   13 use strict;
  3         4  
  3         70  
3 3     3   11 use warnings;
  3         3  
  3         167  
4              
5             package Metabase::Archive::SQL;
6             # ABSTRACT: Metabase archive backend role for common SQL actions
7              
8             our $VERSION = '1.001';
9              
10 3     3   11 use Moose::Role;
  3         3  
  3         31  
11 3     3   12214 use Moose::Util::TypeConstraints;
  3         5  
  3         25  
12              
13 3     3   4408 use Carp ();
  3         5  
  3         82  
14 3     3   1889 use Compress::Zlib 2 qw(compress uncompress);
  3         133158  
  3         252  
15 3     3   22 use DBI 1 ();
  3         54  
  3         51  
16 3     3   1439 use DBIx::RunSQL;
  3         3952  
  3         86  
17 3     3   1541 use DBIx::Simple;
  3         12494  
  3         97  
18 3     3   1660 use Data::Stream::Bulk::Array;
  3         243876  
  3         150  
19 3     3   1883 use Data::Stream::Bulk::DBI;
  3         81254  
  3         106  
20 3     3   20 use Data::Stream::Bulk::Filter;
  3         6  
  3         65  
21 3     3   671 use File::Temp ();
  3         7282  
  3         58  
22 3     3   1944 use JSON 2 ();
  3         24458  
  3         89  
23 3     3   1410 use List::AllUtils qw/uniq/;
  3         21374  
  3         227  
24 3     3   1383 use Metabase::Fact;
  3         24562  
  3         110  
25 3     3   2206 use SQL::Abstract;
  3         22401  
  3         227  
26 3     3   1608 use SQL::Translator 0.11006 (); # required for deploy()
  3         122733  
  3         100  
27 3     3   1556 use SQL::Translator::Diff;
  3         28699  
  3         94  
28 3     3   19 use SQL::Translator::Schema;
  3         5  
  3         51  
29 3     3   11 use SQL::Translator::Schema::Constants;
  3         5  
  3         193  
30 3     3   12 use SQL::Translator::Utils qw/normalize_name/;
  3         4  
  3         108  
31 3     3   11 use Try::Tiny;
  3         4  
  3         2102  
32              
33             with 'Metabase::Backend::SQL';
34             with 'Metabase::Archive' => { -version => 1.000 };
35              
36             has 'compressed' => (
37             is => 'rw',
38             isa => 'Bool',
39             default => 1,
40             );
41              
42             has _table_name => (
43             is => 'ro',
44             isa => 'Str',
45             default => 'metabase_archive',
46             );
47              
48             sub initialize {
49 9     9 0 403625 my ($self, @fact_classes) = @_;
50 9         308 my $schema = $self->schema;
51 9         313 my $table = SQL::Translator::Schema::Table->new( name => $self->_table_name );
52             $table->add_field(
53             name => 'guid',
54             is_nullable => 0,
55 9 50       3036 %{$self->_guid_field_params}
  9         338  
56             ) or die;
57             $table->add_field(
58             name => 'fact',
59             is_nullable => 0,
60 9 50       10381 %{$self->_blob_field_params}
  9         372  
61             ) or die;
62 9         10726 $table->add_constraint(
63             name => $self->_table_name . "_pk",
64             fields => ['guid'],
65             type => PRIMARY_KEY,
66             );
67 9         14431 $schema->add_table($table);
68 9         2979 $self->_deploy_schema;
69 9         52 return;
70             }
71              
72             # given fact, store it and return guid; return
73             # XXX can we store a fact with a GUID already? Replaces? Or error?
74             # here assign only if no GUID already
75             sub store {
76 10     10 0 3307 my ( $self, $fact_struct ) = @_;
77 10         51 my $guid = lc $fact_struct->{metadata}{core}{guid};
78              
79 10 50       34 unless ($guid) {
80 0         0 Carp::confess "Can't store: no GUID set for fact\n";
81             }
82              
83             # remove any metadata that can be regenerated
84             my $fact = {
85             content => $fact_struct->{content},
86             metadata => { core => $fact_struct->{metadata}{core} },
87 10         61 };
88              
89 10         22 my $json = eval { JSON->new->utf8->encode($fact) };
  10         499  
90 10 50       33 Carp::confess "Couldn't convert to JSON: $@"
91             unless $json;
92              
93             # if ( $self->compressed ) {
94             # $json = compress($json);
95             # }
96              
97              
98             try {
99 10     10   785 $self->dbis->begin_work();
100 10         607 $self->dbis->insert($self->_table_name, {
101             guid => $self->_munge_guid($guid),
102             fact => $json,
103             });
104 10         6322 $self->dbis->commit;
105             }
106             catch {
107 0     0   0 $self->dbis->rollback;
108 0         0 Carp::confess("Error inserting record: $_");
109 10         112 };
110              
111 10         1507 return $guid;
112             }
113              
114             # given guid, retrieve it and return it
115             # type is directory path
116             # class isa Metabase::Fact::Subclass
117             sub extract {
118 2     2 0 1031 my ( $self, $guid ) = @_;
119 2         74 my $rs = $self->dbis->select($self->_table_name, 'fact', {
120             guid => $self->_munge_guid($guid)
121             });
122 2         849 return $self->_extract_fact($rs->fetch->[0]);
123             }
124              
125             sub _extract_fact {
126 8     8   135 my ($self, $json) = @_;
127 8 50       40 return unless $json;
128              
129             # if ( $self->compressed ) {
130             # $json = uncompress($json);
131             # }
132              
133 8         16 my $fact = eval { JSON->new->utf8->decode($json) };
  8         227  
134 8 50       31 Carp::confess "Couldn't convert from JSON: $@"
135             unless $fact;
136              
137 8         37 return $fact;
138             }
139              
140             sub delete {
141 2     2 0 392 my ( $self, $guid ) = @_;
142              
143 2         3 my $rs;
144             try {
145 2     2   118 $self->dbis->begin_work();
146 2         90 $rs = $self->dbis->delete($self->_table_name, {
147             guid => $self->_munge_guid($guid)
148             });
149 2         964 $self->dbis->commit;
150             }
151             catch {
152 0     0   0 $self->dbis->rollback;
153 0         0 Carp::confess("Error deleting record: $_");
154 2         22 };
155              
156 2         249 return $rs->rows;
157             }
158              
159             sub iterator {
160 6     6 0 755 my ($self) = @_;
161 6         211 my $rs = $self->dbis->select($self->_table_name, 'fact'); # everything
162              
163 6         1801 my $sth = $rs->{st}{sth}; # XXX encapsulation violation, oh, well
164              
165             # Not all DB's set 'Active' on the sth correctly
166             # so fall back to fetching all data if it can't
167 6         12 my $dbi_stream;
168 6         11 if ( 0 && $sth->FETCH('Active') ) {
169             $dbi_stream = Data::Stream::Bulk::DBI->new(
170             sth => $sth
171             );
172             }
173             else {
174 6         31 $dbi_stream = Data::Stream::Bulk::Array->new(
175             array => scalar $rs->arrays,
176             );
177             }
178              
179             return Data::Stream::Bulk::Filter->new(
180             stream => $dbi_stream,
181             filter => sub {
182 6     6   1594 my $block = shift;
183 6         20 return [ map { $self->_extract_fact($_->[0]) } @$block ];
  6         27  
184             },
185 6         850 );
186             }
187              
188             1;
189              
190             __END__
191              
192             =pod
193              
194             =encoding UTF-8
195              
196             =head1 NAME
197              
198             Metabase::Archive::SQL - Metabase archive backend role for common SQL actions
199              
200             =head1 VERSION
201              
202             version 1.001
203              
204             =head1 SYNOPSIS
205              
206             package Metabase::Archive::SQLite;
207              
208             use Moose;
209              
210             with 'Metabase::Archive::SQL';
211              
212             # implement required fields
213             ...;
214              
215             1;
216              
217             =head1 DESCRIPTION
218              
219             This is a role that consumes the L<Metabase::Backend::SQL> role and implements
220             the L<Metabase::Archive> role generically for an SQL backend. RDBMS vendor
221             specific methods must be implemented by a Moose class consuming this role.
222              
223             The following methods must be implemented:
224              
225             _build_dsn # a DSN string for DBI
226             _build_db_user # a username for DBI
227             _build_db_pass # a password for DBI
228             _build_db_type # a SQL::Translator type for the DB vendor
229             _build_typemap # hashref of metadata types to schema data types
230             _build__blob_type # data type for fact blob (compressed JSON)
231              
232             =for Pod::Coverage::TrustPod store extract delete iterator initialize
233             PRIMARY_KEY
234              
235             =head1 AUTHORS
236              
237             =over 4
238              
239             =item *
240              
241             David Golden <dagolden@cpan.org>
242              
243             =item *
244              
245             Leon Brocard <acme@astray.org>
246              
247             =back
248              
249             =head1 COPYRIGHT AND LICENSE
250              
251             This software is Copyright (c) 2011 by David Golden.
252              
253             This is free software, licensed under:
254              
255             The Apache License, Version 2.0, January 2004
256              
257             =cut