add some pod to DBIx::Class::Admin
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Admin.pm
CommitLineData
9f3849c3 1#
2#===============================================================================
3#
4# FILE: Admin.pm
5#
6# DESCRIPTION: Administrative functions for DBIx::Class Schemata
7#
8# FILES: ---
9# BUGS: ---
10# NOTES: ---
11# AUTHOR: Gordon Irving (), <Gordon.irving@sophos.com>
12# VERSION: 1.0
13# CREATED: 28/11/09 12:27:15 GMT
14# REVISION: ---
15#===============================================================================
16
17package DBIx::Class::Admin;
18
19use Moose;
20use MooseX::Types;
21use MooseX::Types::Moose qw/Int HashRef ArrayRef Str Any/;
22use MooseX::Types::Path::Class qw(Dir File);
9f3849c3 23use Try::Tiny;
24use parent 'Class::C3::Componentised';
25
26use Data::Dumper;
595cb2c7 27#
28# ['lib|I:s' => 'Additonal library path to search in'],
29# ['schema|s:s' => 'The class of the schema to load', { required => 1 } ],
30# ['config-stanza|S:s' => 'Where in the config to find the connection_info, supply in form MyApp::Model::DB',],
31# ['config|C:s' => 'Supply the config file for parsing by Config::Any', { depends => 'config_stanza'} ],
32# ['connect-info|n:s%' => ' supply the connect info as additonal options ie -I dsn=<dsn> user=<user> password=<pass> '],
33# ['sql-dir|q:s' => 'The directory where sql diffs will be created'],
34# ['sql-type|t:s' => 'The RDBMs falvour you wish to use'],
35# ['version|v:i' => 'Supply a version install'],
36# ['preversion|p:s' => 'The previous version to diff against',],
37#
38# 'schema=s' => \my $schema_class,
39# 'class=s' => \my $resultset_class,
40# 'connect=s' => \my $connect,
41# 'op=s' => \my $op,
42# 'set=s' => \my $set,
43# 'where=s' => \my $where,
44# 'attrs=s' => \my $attrs,
45# 'format=s' => \my $format,
46# 'force' => \my $force,
47# 'trace' => \my $trace,
48# 'quiet' => \my $quiet,
49# 'help' => \my $help,
50# 'tlibs' => \my $t_libs,
51#=cut
52
53=head1 NAME
54
55DBIx::Class::Admin - Administration object for schemas
56
57=head1 SYNOPSIS
58
59 use DBIx::Class::Admin;
60
61 # ddl manipulation
62 my $admin = DBIx::Class::Admin->new(
63 schema_class=> 'MY::Schema',
64 sql_dir=> $sql_dir,
65 connect_info => { dsn => $dsn, user => $user, password => $pass },
66 );
67
68 # create SQLite sql
69 $admin->create('SQLite');
70
71 # create SQL diff for an upgrade
72 $admin->create('SQLite', {} , "1.0");
73
74 # upgrade a database
75 $admin->upgrade();
76
77 # install a version for an unversioned schema
78 $admin->install("3.0");
9f3849c3 79
80=head1 Attributes
81
595cb2c7 82=head2 lib
83
84add a library search path
9f3849c3 85=cut
86has lib => (
87 is => 'ro',
88 isa => Dir,
89 coerce => 1,
90 trigger => \&_set_inc,
91);
92
93sub _set_inc {
94 my ($self, $lib) = @_;
95 push @INC, $lib->stringify;
96}
97
595cb2c7 98=head2 schema_class
9f3849c3 99
595cb2c7 100the class of the schema to load
101=cut
9f3849c3 102has 'schema_class' => (
103 is => 'ro',
104 isa => 'Str',
105 coerce => 1,
106);
107
595cb2c7 108=head2 schema
9f3849c3 109
595cb2c7 110A pre-connected schema object can be provided for manipulation
111=cut
9f3849c3 112has 'schema' => (
113 is => 'ro',
114 isa => 'DBIx::Class::Schema',
115 lazy_build => 1,
116);
117
118
119
120sub _build_schema {
121 my ($self) = @_;
122 $self->ensure_class_loaded($self->schema_class);
9f3849c3 123
2ded40e7 124 $self->connect_info->[3]->{ignore_version} =1;
2ded40e7 125 return $self->schema_class->connect(@{$self->connect_info()} ); # , $self->connect_info->[3], { ignore_version => 1} );
9f3849c3 126}
127
595cb2c7 128=head2 connect_info
129
130connect_info the arguments to provide to the connect call of the schema_class
131=cut
9f3849c3 132has 'connect_info' => (
133 is => 'ro',
134 isa => ArrayRef,
135 lazy_build => 1,
136);
137
138sub _build_connect_info {
139 my ($self) = @_;
595cb2c7 140 return $self->_find_stanza($self->config, $self->config_stanza);
9f3849c3 141}
142
595cb2c7 143=head2 config_file
144
145config_file provide a config_file to read connect_info from, if this is provided
146config_stanze should also be provided to locate where the connect_info is in the config
147The config file should be in a format readable by Config::General
148=cut
149has config_file => (
150 is => 'ro',
151 isa => File,
152 coerce => 1,
153);
154
155=head2 config_stanza
156
157config_stanza for use with config_file should be a '::' deliminated 'path' to the connection information
158designed for use with catalyst config files
159=cut
160has 'config_stanza' => (
161 is => 'ro',
162 isa => 'Str',
163);
164
165=head2 config
166
167Instead of loading from a file the configuration can be provided directly as a hash ref. Please note
168config_stanza will still be required.
169=cut
9f3849c3 170has config => (
171 is => 'ro',
172 isa => HashRef,
173 lazy_build => 1,
174);
175
176sub _build_config {
177 my ($self) = @_;
178 try { require 'Config::Any'; } catch { die "Config::Any is required to parse the config file"; };
179
180 my $cfg = Config::Any->load_files ( {files => [$self->config_file], use_ext =>1, flatten_to_hash=>1});
181
182 # just grab the config from the config file
183 $cfg = $cfg->{$self->config_file};
184 return $cfg;
185}
186
595cb2c7 187=head2 sql_dir
9f3849c3 188
595cb2c7 189The location where sql ddl files should be created or found for an upgrade.
190=cut
9f3849c3 191has 'sql_dir' => (
192 is => 'ro',
193 isa => Dir,
194 coerce => 1,
195);
196
595cb2c7 197=head2 version
9f3849c3 198
595cb2c7 199Used for install, the version which will be 'installed' in the schema
200=cut
9f3849c3 201has version => (
912e2d5a 202 is => 'rw',
9f3849c3 203 isa => 'Str',
204);
205
595cb2c7 206=head2 preversion
207
208Previouse 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
209=cut
9f3849c3 210has preversion => (
211 is => 'rw',
212 isa => 'Str',
9f3849c3 213);
214
595cb2c7 215=head2 force
216
217Try and force certain operations.
218=cut
912e2d5a 219has force => (
220 is => 'rw',
221 isa => 'Bool',
222);
223
595cb2c7 224=head2 quite
225
226Be less verbose about actions
227=cut
64c012f4 228has quiet => (
229 is => 'rw',
230 isa => 'Bool',
231);
232
912e2d5a 233has '_confirm' => (
595cb2c7 234 is => 'bare',
912e2d5a 235 isa => 'Bool',
236);
237
595cb2c7 238=head1 METHODS
239
240=head2 create
241
242=over 4
243
244=item Arguments: $sqlt_type, \%sqlt_args, $preversion
245
246=back
247
248L<create> will generate sql for the supplied schema_class in sql_dir. The flavour of sql to
249generate can be controlled by suppling a sqlt_type which should be a L<SQL::Translator> name.
250
251Arguments for L<SQL::Translator> can be supplied in the sqlt_args hashref.
252
253Optional preversion can be supplied to generate a diff to be used by upgrade.
254=cut
255
9f3849c3 256sub create {
595cb2c7 257 my ($self, $sqlt_type, $sqlt_args, $preversion) = @_;
258
259 $preversion ||= $self->preversion();
260
9f3849c3 261 my $schema = $self->schema();
9f3849c3 262 # create the dir if does not exist
263 $self->sql_dir->mkpath() if ( ! -d $self->sql_dir);
264
595cb2c7 265 $schema->create_ddl_dir( $sqlt_type, (defined $schema->schema_version ? $schema->schema_version : ""), $self->sql_dir->stringify, $preversion, $sqlt_args );
9f3849c3 266}
267
595cb2c7 268=head2 upgrade
269
270=over 4
271
272=item Arguments: <none>
273
274=back
275
276upgrade will attempt to upgrade the connected database to the same version as the schema_class.
277B<MAKE SURE YOU BACKUP YOUR DB FIRST>
278=cut
279
9f3849c3 280sub upgrade {
281 my ($self) = @_;
282 my $schema = $self->schema();
283 if (!$schema->get_db_version()) {
284 # schema is unversioned
912e2d5a 285 die "could not determin current schema version, please either install or deploy";
9f3849c3 286 } else {
64c012f4 287 my $ret = $schema->upgrade();
288 return $ret;
9f3849c3 289 }
290}
291
595cb2c7 292=head2 install
293
294=over 4
295
296=item Arguments: $version
297
298=back
299
300install is here to help when you want to move to L<DBIx::Class::Schema::Versioned> and have an existing
301database. install will take a version and add the version tracking tables and 'install' the version. No
302further ddl modification takes place. Setting the force attribute to a true value will allow overriding of
303already versioned databases.
304=cut
9f3849c3 305sub install {
912e2d5a 306 my ($self, $version) = @_;
9f3849c3 307
308 my $schema = $self->schema();
912e2d5a 309 $version ||= $self->version();
310 if (!$schema->get_db_version() ) {
9f3849c3 311 # schema is unversioned
912e2d5a 312 print "Going to install schema version\n";
313 my $ret = $schema->install($version);
314 print "retun is $ret\n";
315 }
316 elsif ($schema->get_db_version() and $self->force ) {
317 warn "forcing install may not be a good idea";
595cb2c7 318 if($self->_confirm() ) {
912e2d5a 319 # FIXME private api
912e2d5a 320 $self->schema->_set_db_version({ version => $version});
321 }
322 }
323 else {
324 die "schema already has a version not installing, try upgrade instead";
9f3849c3 325 }
326
327}
328
595cb2c7 329=head2 deploy
330
331=over 4
332
333=item Arguments: $args
334
335=back
336
337deploy will create the schema at the connected database. C<$args> are passed straight to
338L<DBIx::Class::Schema/deploy>.
339=cut
9f3849c3 340sub deploy {
2ded40e7 341 my ($self, $args) = @_;
9f3849c3 342 my $schema = $self->schema();
343 if (!$schema->get_db_version() ) {
344 # schema is unversioned
2ded40e7 345 $schema->deploy( $args, $self->sql_dir)
346 or die "could not deploy schema";
9f3849c3 347 } else {
912e2d5a 348 die "there already is a database with a version here, try upgrade instead";
9f3849c3 349 }
350}
351
9f3849c3 352
353# FIXME ensure option spec compatability
354#die('Do not use the where option with the insert op') if ($where);
355#die('Do not use the attrs option with the insert op') if ($attrs);
595cb2c7 356
357=head2 insert_data
358
359=over 4
360
361=item Arguments: $rs, $set
362
363=back
364
365insert_data takes the name of a resultset from the schema_class and a hashref of data to insert
366into that resultset
367
368=cut
9f3849c3 369sub insert_data {
64c012f4 370 my ($self, $rs, $set) = @_;
371 my $resultset = $self->schema->resultset($rs);
9f3849c3 372 my $obj = $resultset->create( $set );
595cb2c7 373 print ''.ref($resultset).' ID: '.join(',',$obj->id())."\n" if (!$self->quiet);
9f3849c3 374}
375
595cb2c7 376
377=head2 update_data
378
379=over 4
380
381=item Arguments: $rs, $set, $where
382
383=back
384
385update_data takes the name of a resultset from the schema_class, a hashref of data to update and
386a where hash used to form the search for the rows to update.
387=cut
9f3849c3 388sub update_data {
882931aa 389 my ($self, $rs, $set, $where) = @_;
390
391 my $resultset = $self->schema->resultset($rs);
392 $resultset = $resultset->search( ($where||{}) );
393
394 my $count = $resultset->count();
395 print "This action will modify $count ".ref($resultset)." records.\n" if (!$self->quiet);
396
595cb2c7 397 if ( $self->force || $self->_confirm() ) {
882931aa 398 $resultset->update_all( $set );
399 }
9f3849c3 400}
401
402# FIXME
403#die('Do not use the set option with the delete op') if ($set);
595cb2c7 404=head2 delete_data
405
406=over 4
407
408=item Arguments: $rs, $where, $attrs
409
410=back
411
412delete_data takes the name of a resultset from the schema_class, a where hashref and a attrs to pass to ->search.
413The found data is deleted and cannot be recovered.
414=cut
9f3849c3 415sub delete_data {
882931aa 416 my ($self, $rs, $where, $attrs) = @_;
9f3849c3 417
882931aa 418 my $resultset = $self->schema->resultset($rs);
419 $resultset = $resultset->search( ($where||{}), ($attrs||()) );
9f3849c3 420
882931aa 421 my $count = $resultset->count();
422 print "This action will delete $count ".ref($resultset)." records.\n" if (!$self->quiet);
9f3849c3 423
595cb2c7 424 if ( $self->force || $self->_confirm() ) {
882931aa 425 $resultset->delete_all();
426 }
9f3849c3 427}
428
595cb2c7 429=head2 select_data
430
431=over 4
432
433=item Arguments: $rs, $where, $attrs
434
435=back
436
437select_data takes the name of a resultset from the schema_class, a where hashref and a attrs to pass to ->search.
438The found data is returned in a array ref where the first row will be the columns list.
439
440=cut
882931aa 441sub select_data {
442 my ($self, $rs, $where, $attrs) = @_;
443
444 my $resultset = $self->schema->resultset($rs);
445 $resultset = $resultset->search( ($where||{}), ($attrs||()) );
446
447 my @data;
448 my @columns = $resultset->result_source->columns();
449 push @data, [@columns];#
450
451 while (my $row = $resultset->next()) {
452 my @fields;
453 foreach my $column (@columns) {
454 push( @fields, $row->get_column($column) );
455 }
456 push @data, [@fields];
9f3849c3 457 }
458
882931aa 459 return \@data;
9f3849c3 460}
461
595cb2c7 462sub _confirm {
882931aa 463 my ($self) = @_;
64c012f4 464 print "Are you sure you want to do this? (type YES to confirm) \n";
912e2d5a 465 # mainly here for testing
595cb2c7 466 return 1 if ($self->meta->get_attribute('_confirm')->get_value($self));
912e2d5a 467 my $response = <STDIN>;
882931aa 468 return 1 if ($response=~/^YES/);
469 return;
9f3849c3 470}
471
595cb2c7 472sub _find_stanza {
473 my ($self, $cfg, $stanza) = @_;
474 my @path = split /::/, $stanza;
475 while (my $path = shift @path) {
476 if (exists $cfg->{$path}) {
477 $cfg = $cfg->{$path};
478 }
479 else {
480 die "could not find $stanza in config, $path did not seem to exist";
481 }
482 }
483 return $cfg;
484}
9f3849c3 4851;