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