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