-#!/usr/bin/perl
+#!/usr/bin/env perl
+
use strict;
use warnings;
-use Getopt::Long;
-use Pod::Usage;
-use JSON qw( jsonToObj );
-
-$JSON::BareKey = 1;
-$JSON::QuotApos = 1;
-
-GetOptions(
- 'schema=s' => \my $schema_class,
- 'class=s' => \my $resultset_class,
- 'connect=s' => \my $connect,
- 'op=s' => \my $op,
- 'set=s' => \my $set,
- 'where=s' => \my $where,
- 'attrs=s' => \my $attrs,
- 'format=s' => \my $format,
- 'force' => \my $force,
- 'trace' => \my $trace,
- 'quiet' => \my $quiet,
- 'help' => \my $help,
- 'tlibs' => \my $t_libs,
-);
-
-if ($t_libs) {
- unshift( @INC, 't/lib', 'lib' );
+BEGIN {
+ require DBIx::Class::Optional::Dependencies;
+ if (my $missing = DBIx::Class::Optional::Dependencies->req_missing_for ('admin_script') ) {
+ die "The following modules are required for the dbicadmin utility: $missing\n";
+ }
}
-pod2usage(1) if ($help);
-$ENV{DBIX_CLASS_STORAGE_DBI_DEBUG} = 1 if ($trace);
-
-die('No op specified') if(!$op);
-die('Invalid op') if ($op!~/^insert|update|delete|select$/s);
-my $csv_class;
-if ($op eq 'select') {
- $format ||= 'tsv';
- die('Invalid format') if ($format!~/^tsv|csv$/s);
- $csv_class = 'Text::CSV_XS';
- eval{ require Text::CSV_XS };
- if ($@) {
- $csv_class = 'Text::CSV_PP';
- eval{ require Text::CSV_PP };
- die('The select op requires either the Text::CSV_XS or the Text::CSV_PP module') if ($@);
- }
-}
-
-die('No schema specified') if(!$schema_class);
-eval("require $schema_class");
-die('Unable to load schema') if ($@);
-$connect = jsonToObj( $connect ) if ($connect);
-my $schema = $schema_class->connect(
- ( $connect ? @$connect : () )
+use DBIx::Class::Admin::Descriptive;
+#use Getopt::Long::Descriptive;
+use DBIx::Class::Admin;
+
+my $short_description = "utility for administrating DBIx::Class schemata";
+my $synopsis_text =q|
+ deploy a schema to a database
+ %c --schema=MyApp::Schema \
+ --connect='["dbi:SQLite:my.db", "", ""]' \
+ --deploy
+
+ update an existing record
+ %c --schema=MyApp::Schema --class=Employee \
+ --connect='["dbi:SQLite:my.db", "", ""]' \
+ --op=update --set='{ "name": "New_Employee" }'
+|;
+
+my ($opts, $usage) = describe_options(
+ "%c: %o",
+ (
+ ['Actions'],
+ ["action" => hidden => { one_of => [
+ ['create' => 'Create version diffs needs preversion'],
+ ['upgrade' => 'Upgrade the database to the current schema'],
+ ['install' => 'Install the schema version tables to an existing database'],
+ ['deploy' => 'Deploy the schema to the database'],
+ ['select' => 'Select data from the schema'],
+ ['insert' => 'Insert data into the schema'],
+ ['update' => 'Update data in the schema'],
+ ['delete' => 'Delete data from the schema'],
+ ['op:s' => 'compatibility option all of the above can be supplied as --op=<action>'],
+ ['help' => 'display this help', { implies => { schema_class => '__dummy__' } } ],
+ ['documentation-as-pod:s' => 'hidden', { implies => { schema_class => '__dummy__' } } ],
+ ], required => 1 }],
+ ['Arguments'],
+ ["configuration" => hidden => { one_of => [
+ ['config-file|config:s' => 'Supply the config file for parsing by Config::Any', { depends => 'config_stanza'} ],
+ ['connect-info:s%' => 'Supply the connect info as trailing options e.g. --connect-info dsn=<dsn> user=<user> password=<pass>' ],
+ ['connect:s' => 'Supply the connect info as a JSON-encoded structure, e.g. an --connect=["dsn","user","pass"]'],
+ ] }],
+ ['schema-class:s' => 'The class of the schema to load', { required => 1 } ],
+ ['config-stanza:s' => 'Where in the config to find the connection_info, supply in form MyApp::Model::DB',],
+ ['resultset|resultset-class|class:s' => 'The resultset to operate on for data manipulation' ],
+ ['sql-dir:s' => 'The directory where sql diffs will be created'],
+ ['sql-type:s' => 'The RDBMs flavour you wish to use'],
+ ['version:i' => 'Supply a version install'],
+ ['preversion:s' => 'The previous version to diff against',],
+ ['set:s' => 'JSON data used to perform data operations' ],
+ ['attrs:s' => 'JSON string to be used for the second argument for search'],
+ ['where:s' => 'JSON string to be used for the where clause of search'],
+ ['force' => 'Be forceful with some operations'],
+ ['trace' => 'Turn on DBIx::Class trace output'],
+ ['quiet' => 'Be less verbose'],
+ ['I:s@' => 'Same as perl\'s -I, prepended to current @INC'],
+ )
);
-die('No class specified') if(!$resultset_class);
-my $resultset = eval{ $schema->resultset($resultset_class) };
-die('Unable to load the class with the schema') if ($@);
-
-$set = jsonToObj( $set ) if ($set);
-$where = jsonToObj( $where ) if ($where);
-$attrs = jsonToObj( $attrs ) if ($attrs);
-
-if ($op eq 'insert') {
- die('Do not use the where option with the insert op') if ($where);
- die('Do not use the attrs option with the insert op') if ($attrs);
- my $obj = $resultset->create( $set );
- print ''.ref($resultset).' ID: '.join(',',$obj->id())."\n";
-}
-elsif ($op eq 'update') {
- $resultset = $resultset->search( ($where||{}) );
- my $count = $resultset->count();
- print "This action will modify $count ".ref($resultset)." records.\n" if (!$quiet);
- if ( $force || confirm() ) {
- $resultset->update_all( $set );
- }
-}
-elsif ($op eq 'delete') {
- die('Do not use the set option with the delete op') if ($set);
- $resultset = $resultset->search( ($where||{}), ($attrs||()) );
- my $count = $resultset->count();
- print "This action will delete $count ".ref($resultset)." records.\n" if (!$quiet);
- if ( $force || confirm() ) {
- $resultset->delete_all();
- }
-}
-elsif ($op eq 'select') {
- die('Do not use the set option with the select op') if ($set);
- my $csv = $csv_class->new({
- sep_char => ( $format eq 'tsv' ? "\t" : ',' ),
- });
- $resultset = $resultset->search( ($where||{}), ($attrs||()) );
- my @columns = $resultset->result_source->columns();
- $csv->combine( @columns );
- print $csv->string()."\n";
- while (my $row = $resultset->next()) {
- my @fields;
- foreach my $column (@columns) {
- push( @fields, $row->get_column($column) );
- }
- $csv->combine( @fields );
- print $csv->string()."\n";
- }
+if(defined (my $fn = $opts->{documentation_as_pod}) ) {
+ $usage->synopsis($synopsis_text);
+ $usage->short_description($short_description);
+
+ my $fh;
+ if ($fn) {
+ require DBIx::Class::_Util;
+ DBIx::Class::_Util::mkdir_p( DBIx::Class::_Util::parent_dir( $fn ) );
+ open( $fh, '>', $fn ) or die "Unable to open $fn: $!\n";
+ }
+ else {
+ $fh = \*STDOUT;
+ }
+
+ print $fh "\n";
+ print $fh $usage->pod;
+ print $fh "\n";
+
+ close $fh if $fn;
+ exit 0;
}
-sub confirm {
- print "Are you sure you want to do this? (type YES to confirm) ";
- my $response = <STDIN>;
- return 1 if ($response=~/^YES/);
- return;
+# FIXME - lowercasing will eventually go away when Getopt::Long::Descriptive is fixed
+if($opts->{i}) {
+ require lib;
+ lib->import( @{delete $opts->{i}} );
}
-__END__
-
-=head1 NAME
-
-dbicadmin - Execute operations upon DBIx::Class objects.
-
-=head1 SYNOPSIS
-
- dbicadmin --op=insert --schema=My::Schema --class=Class --set=JSON
- dbicadmin --op=update --schema=My::Schema --class=Class --set=JSON --where=JSON
- dbicadmin --op=delete --schema=My::Schema --class=Class --where=JSON
- dbicadmin --op=select --schema=My::Schema --class=Class --where=JSON --format=tsv
-
-=head1 DESCRIPTION
-
-This utility provides the ability to run INSERTs, UPDATEs,
-DELETEs, and SELECTs on any DBIx::Class object.
-
-=head1 OPTIONS
-
-=head2 op
-
-The type of operation. Valid values are insert, update, delete,
-and select.
-
-=head2 schema
-
-The name of your schema class.
-
-=head2 class
-
-The name of the class, within your schema, that you want to run
-the operation on.
-
-=head2 connect
-
-A JSON array to be passed to your schema class upon connecting.
-The array will need to be compatible with whatever the DBIC
-->connect() method requires.
-
-=head2 set
-
-This option must be valid JSON data string and is passed in to
-the DBIC update() method. Use this option with the update
-and insert ops.
-
-=head2 where
-
-This option must be valid JSON data string and is passed in as
-the first argument to the DBIC search() method. Use this
-option with the update, delete, and select ops.
-
-=head2 attrs
-
-This option must be valid JSON data string and is passed in as
-the second argument to the DBIC search() method. Use this
-option with the update, delete, and select ops.
-
-=head2 help
-
-Display this help page.
-
-=head2 force
-
-Suppresses the confirmation dialogues that are usually displayed
-when someone runs a DELETE or UPDATE action.
-
-=head2 quiet
-
-Do not display status messages.
-
-=head2 trace
-
-Turns on tracing on the DBI storage, thus printing SQL as it is
-executed.
-
-=head2 tlibs
+if($opts->{help}) {
+ $usage->die();
+}
-This option is purely for testing during the DBIC installation. Do
-not use it.
+# option compatibility mangle
+# (can not be joined in the spec, one is s% the other is s)
+if($opts->{connect}) {
+ $opts->{connect_info} = delete $opts->{connect};
+}
-=head1 JSON
+my $admin = DBIx::Class::Admin->new( %$opts );
-JSON is a lightweight data-interchange format. It allows you
-to express complex data structures for use in the where and
-set options.
+my $action = $opts->{action};
-This module turns on L<JSON>'s BareKey and QuotApos options so
-that your data can look a bit more readable.
+$action = $opts->{op} if ($action eq 'op');
- --where={"this":"that"} # generic JSON
- --where={this:'that'} # with BareKey and QuoteApos
+print "Performing action $action...\n";
-Consider wrapping your JSON in outer quotes so that you don't
-have to escape your inner quotes.
+my $res = $admin->$action();
+if ($action eq 'select') {
- --where={this:\"that\"} # no outer quote
- --where='{this:"that"}' # outer quoted
+ my $format = $opts->{format} || 'tsv';
+ die('Invalid format') if ($format!~/^tsv|csv$/s);
-=head1 AUTHOR
+ require Text::CSV;
-Aran Deltac <bluefeet@cpan.org>
+ my $csv = Text::CSV->new({
+ sep_char => ( $format eq 'tsv' ? "\t" : ',' ),
+ });
-=head1 LICENSE
+ foreach my $row (@$res) {
+ $csv->combine( @$row );
+ print $csv->string()."\n";
+ }
+}
-You may distribute this code under the same terms as Perl itself.
+1;
+__END__