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