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