File Coverage

blib/lib/DBIx/Class/Admin.pm
Criterion Covered Total %
statement 4 4 100.0
branch 1 2 50.0
condition n/a
subroutine 2 2 100.0
pod n/a
total 7 8 87.5


line stmt bran cond sub pod time code
1             package DBIx::Class::Admin;
2              
3             # check deps
4             BEGIN {
5 3     3   46578 use DBIx::Class;
  3         7  
  3         180  
6 3 50   3   22 die('The following modules are required for DBIx::Class::Admin ' . DBIx::Class::Optional::Dependencies->req_missing_for ('admin') )
7             unless DBIx::Class::Optional::Dependencies->req_ok_for ('admin');
8             }
9              
10             use JSON::Any qw(DWIW PP JSON CPANEL XS);
11             use Moose;
12             use MooseX::Types::Moose qw/Int Str Any Bool/;
13             use DBIx::Class::Admin::Types qw/DBICConnectInfo DBICHashRef/;
14             use MooseX::Types::JSON qw(JSON);
15             use MooseX::Types::Path::Class qw(Dir File);
16             use MooseX::Types::LoadableClass qw(LoadableClass);
17             use Try::Tiny;
18             use namespace::clean;
19              
20             =head1 NAME
21              
22             DBIx::Class::Admin - Administration object for schemas
23              
24             =head1 SYNOPSIS
25              
26             $ dbicadmin --help
27              
28             $ dbicadmin --schema=MyApp::Schema \
29             --connect='["dbi:SQLite:my.db", "", ""]' \
30             --deploy
31              
32             $ dbicadmin --schema=MyApp::Schema --class=Employee \
33             --connect='["dbi:SQLite:my.db", "", ""]' \
34             --op=update --set='{ "name": "New_Employee" }'
35              
36             use DBIx::Class::Admin;
37              
38             # ddl manipulation
39             my $admin = DBIx::Class::Admin->new(
40             schema_class=> 'MY::Schema',
41             sql_dir=> $sql_dir,
42             connect_info => { dsn => $dsn, user => $user, password => $pass },
43             );
44              
45             # create SQLite sql
46             $admin->create('SQLite');
47              
48             # create SQL diff for an upgrade
49             $admin->create('SQLite', {} , "1.0");
50              
51             # upgrade a database
52             $admin->upgrade();
53              
54             # install a version for an unversioned schema
55             $admin->install("3.0");
56              
57             =head1 REQUIREMENTS
58              
59             The Admin interface has additional requirements not currently part of
60             L<DBIx::Class>. See L<DBIx::Class::Optional::Dependencies> for more details.
61              
62             =head1 ATTRIBUTES
63              
64             =head2 schema_class
65              
66             the class of the schema to load
67              
68             =cut
69              
70             has 'schema_class' => (
71             is => 'ro',
72             isa => LoadableClass,
73             );
74              
75              
76             =head2 schema
77              
78             A pre-connected schema object can be provided for manipulation
79              
80             =cut
81              
82             has 'schema' => (
83             is => 'ro',
84             isa => 'DBIx::Class::Schema',
85             lazy_build => 1,
86             );
87              
88             sub _build_schema {
89             my ($self) = @_;
90              
91             $self->connect_info->[3]{ignore_version} = 1;
92             return $self->schema_class->connect(@{$self->connect_info});
93             }
94              
95             =head2 resultset
96              
97             a resultset from the schema to operate on
98              
99             =cut
100              
101             has 'resultset' => (
102             is => 'rw',
103             isa => Str,
104             );
105              
106              
107             =head2 where
108              
109             a hash ref or json string to be used for identifying data to manipulate
110              
111             =cut
112              
113             has 'where' => (
114             is => 'rw',
115             isa => DBICHashRef,
116             coerce => 1,
117             );
118              
119              
120             =head2 set
121              
122             a hash ref or json string to be used for inserting or updating data
123              
124             =cut
125              
126             has 'set' => (
127             is => 'rw',
128             isa => DBICHashRef,
129             coerce => 1,
130             );
131              
132              
133             =head2 attrs
134              
135             a hash ref or json string to be used for passing additional info to the ->search call
136              
137             =cut
138              
139             has 'attrs' => (
140             is => 'rw',
141             isa => DBICHashRef,
142             coerce => 1,
143             );
144              
145              
146             =head2 connect_info
147              
148             connect_info the arguments to provide to the connect call of the schema_class
149              
150             =cut
151              
152             has 'connect_info' => (
153             is => 'ro',
154             isa => DBICConnectInfo,
155             lazy_build => 1,
156             coerce => 1,
157             );
158              
159             sub _build_connect_info {
160             my ($self) = @_;
161             return $self->_find_stanza($self->config, $self->config_stanza);
162             }
163              
164              
165             =head2 config_file
166              
167             config_file provide a config_file to read connect_info from, if this is provided
168             config_stanze should also be provided to locate where the connect_info is in the config
169             The config file should be in a format readable by Config::Any.
170              
171             =cut
172              
173             has config_file => (
174             is => 'ro',
175             isa => File,
176             coerce => 1,
177             );
178              
179              
180             =head2 config_stanza
181              
182             config_stanza for use with config_file should be a '::' delimited 'path' to the connection information
183             designed for use with catalyst config files
184              
185             =cut
186              
187             has 'config_stanza' => (
188             is => 'ro',
189             isa => Str,
190             );
191              
192              
193             =head2 config
194              
195             Instead of loading from a file the configuration can be provided directly as a hash ref. Please note
196             config_stanza will still be required.
197              
198             =cut
199              
200             has config => (
201             is => 'ro',
202             isa => DBICHashRef,
203             lazy_build => 1,
204             );
205              
206             sub _build_config {
207             my ($self) = @_;
208              
209             try { require Config::Any }
210             catch { die ("Config::Any is required to parse the config file.\n") };
211              
212             my $cfg = Config::Any->load_files ( {files => [$self->config_file], use_ext =>1, flatten_to_hash=>1});
213              
214             # just grab the config from the config file
215             $cfg = $cfg->{$self->config_file};
216             return $cfg;
217             }
218              
219              
220             =head2 sql_dir
221              
222             The location where sql ddl files should be created or found for an upgrade.
223              
224             =cut
225              
226             has 'sql_dir' => (
227             is => 'ro',
228             isa => Dir,
229             coerce => 1,
230             );
231              
232              
233             =head2 sql_type
234              
235             The type of sql dialect to use for creating sql files from schema
236              
237             =cut
238              
239             has 'sql_type' => (
240             is => 'ro',
241             isa => Str,
242             );
243              
244             =head2 version
245              
246             Used for install, the version which will be 'installed' in the schema
247              
248             =cut
249              
250             has version => (
251             is => 'rw',
252             isa => Str,
253             );
254              
255              
256             =head2 preversion
257              
258             Previous version of the schema to create an upgrade diff for, the full sql for that version of the sql must be in the sql_dir
259              
260             =cut
261              
262             has preversion => (
263             is => 'rw',
264             isa => Str,
265             );
266              
267              
268             =head2 force
269              
270             Try and force certain operations.
271              
272             =cut
273              
274             has force => (
275             is => 'rw',
276             isa => Bool,
277             );
278              
279              
280             =head2 quiet
281              
282             Be less verbose about actions
283              
284             =cut
285              
286             has quiet => (
287             is => 'rw',
288             isa => Bool,
289             );
290              
291             has '_confirm' => (
292             is => 'bare',
293             isa => Bool,
294             );
295              
296              
297             =head2 trace
298              
299             Toggle DBIx::Class debug output
300              
301             =cut
302              
303             has trace => (
304             is => 'rw',
305             isa => Bool,
306             trigger => \&_trigger_trace,
307             );
308              
309             sub _trigger_trace {
310             my ($self, $new, $old) = @_;
311             $self->schema->storage->debug($new);
312             }
313              
314              
315             =head1 METHODS
316              
317             =head2 create
318              
319             =over 4
320              
321             =item Arguments: $sqlt_type, \%sqlt_args, $preversion
322              
323             =back
324              
325             C<create> will generate sql for the supplied schema_class in sql_dir. The
326             flavour of sql to generate can be controlled by supplying a sqlt_type which
327             should be a L<SQL::Translator> name.
328              
329             Arguments for L<SQL::Translator> can be supplied in the sqlt_args hashref.
330              
331             Optional preversion can be supplied to generate a diff to be used by upgrade.
332              
333             =cut
334              
335             sub create {
336             my ($self, $sqlt_type, $sqlt_args, $preversion) = @_;
337              
338             $preversion ||= $self->preversion();
339             $sqlt_type ||= $self->sql_type();
340              
341             my $schema = $self->schema();
342             # create the dir if does not exist
343             $self->sql_dir->mkpath() if ( ! -d $self->sql_dir);
344              
345             $schema->create_ddl_dir( $sqlt_type, (defined $schema->schema_version ? $schema->schema_version : ""), $self->sql_dir->stringify, $preversion, $sqlt_args );
346             }
347              
348              
349             =head2 upgrade
350              
351             =over 4
352              
353             =item Arguments: <none>
354              
355             =back
356              
357             upgrade will attempt to upgrade the connected database to the same version as the schema_class.
358             B<MAKE SURE YOU BACKUP YOUR DB FIRST>
359              
360             =cut
361              
362             sub upgrade {
363             my ($self) = @_;
364             my $schema = $self->schema();
365              
366             if (!$schema->get_db_version()) {
367             # schema is unversioned
368             $schema->throw_exception ("Could not determin current schema version, please either install() or deploy().\n");
369             } else {
370             $schema->upgrade_directory ($self->sql_dir) if $self->sql_dir; # this will override whatever default the schema has
371             my $ret = $schema->upgrade();
372             return $ret;
373             }
374             }
375              
376              
377             =head2 install
378              
379             =over 4
380              
381             =item Arguments: $version
382              
383             =back
384              
385             install is here to help when you want to move to L<DBIx::Class::Schema::Versioned> and have an existing
386             database. install will take a version and add the version tracking tables and 'install' the version. No
387             further ddl modification takes place. Setting the force attribute to a true value will allow overriding of
388             already versioned databases.
389              
390             =cut
391              
392             sub install {
393             my ($self, $version) = @_;
394              
395             my $schema = $self->schema();
396             $version ||= $self->version();
397             if (!$schema->get_db_version() ) {
398             # schema is unversioned
399             print "Going to install schema version\n" if (!$self->quiet);
400             my $ret = $schema->install($version);
401             print "return is $ret\n" if (!$self->quiet);
402             }
403             elsif ($schema->get_db_version() and $self->force ) {
404             warn "Forcing install may not be a good idea\n";
405             if($self->_confirm() ) {
406             $self->schema->_set_db_version({ version => $version});
407             }
408             }
409             else {
410             $schema->throw_exception ("Schema already has a version. Try upgrade instead.\n");
411             }
412              
413             }
414              
415              
416             =head2 deploy
417              
418             =over 4
419              
420             =item Arguments: $args
421              
422             =back
423              
424             deploy will create the schema at the connected database. C<$args> are passed straight to
425             L<DBIx::Class::Schema/deploy>.
426              
427             =cut
428              
429             sub deploy {
430             my ($self, $args) = @_;
431             my $schema = $self->schema();
432             $schema->deploy( $args, $self->sql_dir );
433             }
434              
435             =head2 insert
436              
437             =over 4
438              
439             =item Arguments: $rs, $set
440              
441             =back
442              
443             insert takes the name of a resultset from the schema_class and a hashref of data to insert
444             into that resultset
445              
446             =cut
447              
448             sub insert {
449             my ($self, $rs, $set) = @_;
450              
451             $rs ||= $self->resultset();
452             $set ||= $self->set();
453             my $resultset = $self->schema->resultset($rs);
454             my $obj = $resultset->new_result($set)->insert;
455             print ''.ref($resultset).' ID: '.join(',',$obj->id())."\n" if (!$self->quiet);
456             }
457              
458              
459             =head2 update
460              
461             =over 4
462              
463             =item Arguments: $rs, $set, $where
464              
465             =back
466              
467             update takes the name of a resultset from the schema_class, a hashref of data to update and
468             a where hash used to form the search for the rows to update.
469              
470             =cut
471              
472             sub update {
473             my ($self, $rs, $set, $where) = @_;
474              
475             $rs ||= $self->resultset();
476             $where ||= $self->where();
477             $set ||= $self->set();
478             my $resultset = $self->schema->resultset($rs);
479             $resultset = $resultset->search( ($where||{}) );
480              
481             my $count = $resultset->count();
482             print "This action will modify $count ".ref($resultset)." records.\n" if (!$self->quiet);
483              
484             if ( $self->force || $self->_confirm() ) {
485             $resultset->update_all( $set );
486             }
487             }
488              
489              
490             =head2 delete
491              
492             =over 4
493              
494             =item Arguments: $rs, $where, $attrs
495              
496             =back
497              
498             delete takes the name of a resultset from the schema_class, a where hashref and a attrs to pass to ->search.
499             The found data is deleted and cannot be recovered.
500              
501             =cut
502              
503             sub delete {
504             my ($self, $rs, $where, $attrs) = @_;
505              
506             $rs ||= $self->resultset();
507             $where ||= $self->where();
508             $attrs ||= $self->attrs();
509             my $resultset = $self->schema->resultset($rs);
510             $resultset = $resultset->search( ($where||{}), ($attrs||()) );
511              
512             my $count = $resultset->count();
513             print "This action will delete $count ".ref($resultset)." records.\n" if (!$self->quiet);
514              
515             if ( $self->force || $self->_confirm() ) {
516             $resultset->delete_all();
517             }
518             }
519              
520              
521             =head2 select
522              
523             =over 4
524              
525             =item Arguments: $rs, $where, $attrs
526              
527             =back
528              
529             select takes the name of a resultset from the schema_class, a where hashref and a attrs to pass to ->search.
530             The found data is returned in a array ref where the first row will be the columns list.
531              
532             =cut
533              
534             sub select {
535             my ($self, $rs, $where, $attrs) = @_;
536              
537             $rs ||= $self->resultset();
538             $where ||= $self->where();
539             $attrs ||= $self->attrs();
540             my $resultset = $self->schema->resultset($rs);
541             $resultset = $resultset->search( ($where||{}), ($attrs||()) );
542              
543             my @data;
544             my @columns = $resultset->result_source->columns();
545             push @data, [@columns];#
546              
547             while (my $row = $resultset->next()) {
548             my @fields;
549             foreach my $column (@columns) {
550             push( @fields, $row->get_column($column) );
551             }
552             push @data, [@fields];
553             }
554              
555             return \@data;
556             }
557              
558             sub _confirm {
559             my ($self) = @_;
560              
561             # mainly here for testing
562             return 1 if ($self->meta->get_attribute('_confirm')->get_value($self));
563              
564             print "Are you sure you want to do this? (type YES to confirm) \n";
565             my $response = <STDIN>;
566              
567             return ($response=~/^YES/);
568             }
569              
570             sub _find_stanza {
571             my ($self, $cfg, $stanza) = @_;
572             my @path = split /::/, $stanza;
573             while (my $path = shift @path) {
574             if (exists $cfg->{$path}) {
575             $cfg = $cfg->{$path};
576             }
577             else {
578             die ("Could not find $stanza in config, $path does not seem to exist.\n");
579             }
580             }
581             $cfg = $cfg->{connect_info} if exists $cfg->{connect_info};
582             return $cfg;
583             }
584              
585             =head1 FURTHER QUESTIONS?
586              
587             Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
588              
589             =head1 COPYRIGHT AND LICENSE
590              
591             This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
592             by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
593             redistribute it and/or modify it under the same terms as the
594             L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.
595              
596             =cut
597              
598             1;