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   341 use Moose;
  51         108  
  51         373  
6 51     51   313016 use MooseX::StrictConstructor;
  51         134  
  51         428  
7 51     51   166202 use MooseX::ClassAttribute;
  51         2780654  
  51         252  
8 51     51   9633779 use MooseX::MarkAsMethods ( autoclean => 1 );
  51         133  
  51         492  
9 51     51   449695 use MooseX::Types::Moose qw(Str);
  51         128  
  51         546  
10              
11 51     51   227293 use Path::Class qw(file);
  51         127  
  51         3443  
12              
13 51     51   20809 use Pinto::Schema;
  51         192  
  51         2419  
14 51     51   414 use Pinto::Types qw(File);
  51         104  
  51         474  
15 51     51   304929 use Pinto::Util qw(debug throw);
  51         114  
  51         30003  
16              
17             #-------------------------------------------------------------------------------
18              
19             our $VERSION = '0.13'; # 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   1135 my ($self) = @_;
50              
51 332         11179 my $schema = Pinto::Schema->new;
52              
53 332         8900 my $db_file = $self->repo->config->db_file;
54 332         2085 my $dsn = "dbi:SQLite:$db_file";
55 332         15435 my $xtra = { on_connect_call => 'use_foreign_keys' };
56 332         1307 my @args = ( $dsn, undef, undef, $xtra );
57              
58 332         3567 my $connected = $schema->connect(@args);
59              
60             # Inject attributes thru back door
61 332         3514271 $connected->repo( $self->repo );
62              
63             # Tune sqlite (taken from monotone)...
64 332         6828 my $dbh = $connected->storage->dbh;
65 332         2075582 $dbh->do('PRAGMA page_size = 8192');
66 332         11756 $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         128155 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 406 my ($self) = @_;
96              
97 113         2985 my $db_dir = $self->repo->config->db_dir;
98 113         659 debug("Makding db directory at $db_dir");
99 113         620 $db_dir->mkpath;
100              
101 113         9664 my $guard = $self->schema->storage->txn_scope_guard;
102 113         80088 $self->create_database_schema;
103 113         752 $self->create_root_revision;
104 113         842538 $guard->commit;
105              
106 113         1437203 return $self;
107             }
108              
109             #-------------------------------------------------------------------------------
110              
111             sub create_database_schema {
112 113     113 0 350 my ($self) = @_;
113              
114 113         790 debug("Creating database schema");
115              
116 113         3414 my $dbh = $self->schema->storage->dbh;
117 113         50170 $dbh->do("$_;") for split /;/, $self->ddl;
118              
119 113         267338 return $self;
120             }
121              
122             #-------------------------------------------------------------------------------
123              
124             sub create_root_revision {
125 113     113 0 377 my ($self) = @_;
126              
127 113         545 my $attrs = {
128             uuid => $self->root_revision_uuid,
129             message => 'root commit',
130             is_committed => 1
131             };
132              
133 113         647 debug("Creating root revision");
134              
135 113         4066 return $self->schema->create_revision($attrs);
136             }
137              
138             #-------------------------------------------------------------------------------
139              
140             sub get_root_revision {
141 129     129 0 519 my ($self) = @_;
142              
143 129         667 my $where = { uuid => $self->root_revision_uuid };
144 129         795 my $attrs = { key => 'uuid_unique' };
145              
146 129 50       3601 my $revision = $self->schema->find_revision( $where, $attrs )
147             or throw "PANIC: No root revision was found";
148              
149 129         3462 return $revision;
150             }
151              
152             #-------------------------------------------------------------------------------
153              
154 242     242 0 1582 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.13
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