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