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