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