File Coverage

blib/lib/Pinto/Database.pm
Criterion Covered Total %
statement 63 63 100.0
branch 1 2 50.0
condition n/a
subroutine 15 15 100.0
pod 0 5 0.0
total 79 85 92.9


line stmt bran cond sub pod time code
1             # ABSTRACT: Interface to the Pinto database
2              
3             package Pinto::Database;
4              
5 51     51   374 use Moose;
  51         116  
  51         423  
6 51     51   334865 use MooseX::StrictConstructor;
  51         110  
  51         450  
7 51     51   170729 use MooseX::ClassAttribute;
  51         2737468  
  51         287  
8 51     51   9539522 use MooseX::MarkAsMethods ( autoclean => 1 );
  51         132  
  51         509  
9 51     51   480476 use MooseX::Types::Moose qw(Str);
  51         130  
  51         566  
10              
11 51     51   230870 use Path::Class qw(file);
  51         110  
  51         3764  
12              
13 51     51   22333 use Pinto::Schema;
  51         204  
  51         2595  
14 51     51   438 use Pinto::Types qw(File);
  51         106  
  51         484  
15 51     51   300239 use Pinto::Util qw(debug throw);
  51         130  
  51         32991  
16              
17             #-------------------------------------------------------------------------------
18              
19             our $VERSION = '0.14'; # VERSION
20              
21             #-------------------------------------------------------------------------------
22              
23             has repo => (
24             is => 'ro',
25             isa => 'Pinto::Repository',
26             weak_ref => 1,
27             required => 1,
28             );
29              
30             has schema => (
31             is => 'ro',
32             isa => 'Pinto::Schema',
33             builder => '_build_schema',
34             init_arg => undef,
35             lazy => 1,
36             );
37              
38             class_has ddl => (
39             is => 'ro',
40             isa => Str,
41             init_arg => undef,
42             default => do { local $/ = undef; <DATA> },
43             lazy => 1,
44             );
45              
46             #-------------------------------------------------------------------------------
47              
48             sub _build_schema {
49 332     332   1036 my ($self) = @_;
50              
51 332         10366 my $schema = Pinto::Schema->new;
52              
53 332         8456 my $db_file = $self->repo->config->db_file;
54 332         1933 my $dsn = "dbi:SQLite:$db_file";
55 332         14326 my $xtra = { on_connect_call => 'use_foreign_keys' };
56 332         1333 my @args = ( $dsn, undef, undef, $xtra );
57              
58 332         3510 my $connected = $schema->connect(@args);
59              
60             # Inject attributes thru back door
61 332         3448861 $connected->repo( $self->repo );
62              
63             # Tune sqlite (taken from monotone)...
64 332         6357 my $dbh = $connected->storage->dbh;
65 332         2055980 $dbh->do('PRAGMA page_size = 8192');
66 332         11232 $dbh->do('PRAGMA cache_size = 4000');
67              
68             # These may be unhelpful or unwise...
69             #$dbh->do('PRAGMA temp_store = MEMORY');
70             #$dbh->do('PRAGMA journal_mode = WAL');
71             #$dbh->do('PRAGMA synchronous = OFF');
72              
73 332         122901 return $connected;
74             }
75              
76             #-------------------------------------------------------------------------------
77             # NB: We used to just let DBIx::Class generate the DDL from its own schema, but
78             # SQL::Translator does not support the COLLATE feature of SQLite. So now, we
79             # ship Pinto with a real copy of the DDL, and feed it into the database when
80             # the repository is initialized.
81             #
82             # Personally, I kinda prefer having a raw DDL file, rather than generating it
83             # because then I know *exactly* what the database schema will be, and we are
84             # no longer exposed to bugs that might exist in SQL::Translator. We don't need
85             # to deploy to different RDBMSes, so we don't really need SQL::Translator to
86             # help with that anyway.
87             #
88             # DBD::SQLite can only process one statement at a time, so we have to parse
89             # the file and "do" each statement separately. Splitting on semicolons is
90             # primitive, but effective (as long as semicolons are only used in statement
91             # terminators).
92             #-------------------------------------------------------------------------------
93              
94             sub deploy {
95 113     113 0 427 my ($self) = @_;
96              
97 113         2807 my $db_dir = $self->repo->config->db_dir;
98 113         678 debug("Makding db directory at $db_dir");
99 113         628 $db_dir->mkpath;
100              
101 113         8901 my $guard = $self->schema->storage->txn_scope_guard;
102 113         78525 $self->create_database_schema;
103 113         729 $self->create_root_revision;
104 113         852498 $guard->commit;
105              
106 113         1582461 return $self;
107             }
108              
109             #-------------------------------------------------------------------------------
110              
111             sub create_database_schema {
112 113     113 0 356 my ($self) = @_;
113              
114 113         741 debug("Creating database schema");
115              
116 113         3366 my $dbh = $self->schema->storage->dbh;
117 113         49423 $dbh->do("$_;") for split /;/, $self->ddl;
118              
119 113         261710 return $self;
120             }
121              
122             #-------------------------------------------------------------------------------
123              
124             sub create_root_revision {
125 113     113 0 485 my ($self) = @_;
126              
127 113         593 my $attrs = {
128             uuid => $self->root_revision_uuid,
129             message => 'root commit',
130             is_committed => 1
131             };
132              
133 113         639 debug("Creating root revision");
134              
135 113         4028 return $self->schema->create_revision($attrs);
136             }
137              
138             #-------------------------------------------------------------------------------
139              
140             sub get_root_revision {
141 129     129 0 442 my ($self) = @_;
142              
143 129         622 my $where = { uuid => $self->root_revision_uuid };
144 129         671 my $attrs = { key => 'uuid_unique' };
145              
146 129 50       3267 my $revision = $self->schema->find_revision( $where, $attrs )
147             or throw "PANIC: No root revision was found";
148              
149 129         3362 return $revision;
150             }
151              
152             #-------------------------------------------------------------------------------
153              
154 242     242 0 1429 sub root_revision_uuid { return '00000000-0000-0000-0000-000000000000' }
155              
156             #-------------------------------------------------------------------------------
157              
158             __PACKAGE__->meta->make_immutable;
159              
160             #-------------------------------------------------------------------------------
161              
162             1;
163              
164             =pod
165              
166             =encoding UTF-8
167              
168             =for :stopwords Jeffrey Ryan Thalhammer
169              
170             =head1 NAME
171              
172             Pinto::Database - Interface to the Pinto database
173              
174             =head1 VERSION
175              
176             version 0.14
177              
178             =head1 AUTHOR
179              
180             Jeffrey Ryan Thalhammer <jeff@stratopan.com>
181              
182             =head1 COPYRIGHT AND LICENSE
183              
184             This software is copyright (c) 2015 by Jeffrey Ryan Thalhammer.
185              
186             This is free software; you can redistribute it and/or modify it under
187             the same terms as the Perl 5 programming language system itself.
188              
189             =cut
190              
191             __DATA__
192              
193             CREATE TABLE distribution (
194             id INTEGER PRIMARY KEY NOT NULL,
195             author TEXT NOT NULL COLLATE NOCASE,
196             archive TEXT NOT NULL,
197             source TEXT NOT NULL,
198             mtime INTEGER NOT NULL,
199             sha256 TEXT NOT NULL,
200             md5 TEXT NOT NULL,
201             metadata TEXT NOT NULL,
202              
203             UNIQUE(author, archive)
204             );
205              
206              
207             CREATE TABLE package (
208             id INTEGER PRIMARY KEY NOT NULL,
209             name TEXT NOT NULL,
210             version TEXT NOT NULL,
211             file TEXT DEFAULT NULL,
212             sha256 TEXT DEFAULT NULL,
213             distribution INTEGER NOT NULL REFERENCES distribution(id) ON DELETE CASCADE,
214              
215             UNIQUE(name, distribution)
216             );
217              
218              
219             CREATE TABLE stack (
220             id INTEGER PRIMARY KEY NOT NULL,
221             name TEXT NOT NULL UNIQUE COLLATE NOCASE,
222             is_default BOOLEAN NOT NULL,
223             is_locked BOOLEAN NOT NULL,
224             properties TEXT NOT NULL,
225             head INTEGER NOT NULL REFERENCES revision(id) ON DELETE RESTRICT
226             );
227              
228              
229             CREATE TABLE registration (
230             id INTEGER PRIMARY KEY NOT NULL,
231             revision INTEGER NOT NULL REFERENCES revision(id) ON DELETE CASCADE,
232             package_name TEXT NOT NULL,
233             package INTEGER NOT NULL REFERENCES package(id) ON DELETE CASCADE,
234             distribution INTEGER NOT NULL REFERENCES distribution(id) ON DELETE CASCADE,
235             is_pinned BOOLEAN NOT NULL,
236              
237             UNIQUE(revision, package_name)
238             );
239              
240              
241             CREATE TABLE revision (
242             id INTEGER PRIMARY KEY NOT NULL,
243             uuid TEXT NOT NULL UNIQUE,
244             message TEXT NOT NULL,
245             username TEXT NOT NULL,
246             utc_time INTEGER NOT NULL,
247             time_offset INTEGER NOT NULL,
248             is_committed BOOLEAN NOT NULL,
249             has_changes BOOLEAN NOT NULL
250             );
251              
252              
253             CREATE TABLE ancestry (
254             id INTEGER PRIMARY KEY NOT NULL,
255             parent INTEGER NOT NULL REFERENCES revision(id) ON DELETE CASCADE,
256             child INTEGER NOT NULL REFERENCES revision(id) ON DELETE CASCADE
257             );
258              
259              
260             CREATE TABLE prerequisite (
261             id INTEGER PRIMARY KEY NOT NULL,
262             phase TEXT NOT NULL,
263             distribution INTEGER NOT NULL REFERENCES distribution(id) ON DELETE CASCADE,
264             package_name TEXT NOT NULL,
265             package_version TEXT NOT NULL,
266              
267             UNIQUE(distribution, phase, package_name)
268             );
269              
270             CREATE INDEX idx_ancestry_parent ON ancestry(parent);
271             CREATE INDEX idx_ancestry_child ON ancestry(child);
272             CREATE INDEX idx_package_sha256 ON package(sha256);
273             CREATE INDEX idx_distribution_sha256 ON distribution(sha256);
274