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