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