File Coverage

lib/UR/Namespace/Command/Define/Db.pm
Criterion Covered Total %
statement 12 124 9.6
branch 0 44 0.0
condition 0 3 0.0
subroutine 4 16 25.0
pod 0 3 0.0
total 16 190 8.4


line stmt bran cond sub pod time code
1             package UR::Namespace::Command::Define::Db;
2              
3 1     1   23 use warnings;
  1         1  
  1         28  
4 1     1   3 use strict;
  1         1  
  1         15  
5 1     1   3 use UR;
  1         1  
  1         6  
6             our $VERSION = "0.46"; # UR $VERSION;
7 1     1   3 use IO::File; # required to import symbols used below
  1         1  
  1         1258  
8              
9             UR::Object::Type->define(
10             class_name => __PACKAGE__,
11             is => "UR::Namespace::Command::Base",
12             has_input => [
13             uri => {
14             is => 'Text',
15             shell_args_position => 1,
16             doc => 'a DBI connect string like dbi:mysql:someserver or user/passwd@dbi:Oracle:someserver~defaultns'
17             },
18             name => {
19             is => 'Text',
20             shell_args_position => 2,
21             default_value => 'Db1',
22             doc => "the name for this data source (used for class naming)",
23             },
24             ],
25             has_output_optional => [
26             _class_name=> {
27             is => 'Text',
28             calculate_from => ['name'],
29             calculate => q|
30             my $namespace = $self->namespace_name;
31             my $dsid = $namespace . '::DataSource::' . $name;
32             return $dsid
33             |,
34             doc => "The full class name to give this data source.",
35             },
36             _ds => {
37             is_transient => 1,
38             },
39             ],
40             doc => 'add a data source to the current namespace'
41             );
42              
43 0     0 0   sub sub_command_sort_position { 2 }
44              
45             sub help_synopsis {
46             return <<'EOS'
47             ur define db dbi:SQLite:/some/file.db Db1
48              
49             ur define db me@dbi:mysql:myserver MainDb
50              
51             ur define db me@dbi:Oracle:someserver ProdDb
52             ur define db me@dbi:Oracle:someserver~schemaname BigDb
53              
54             ur define db me@dbi:Pg:prod Db1
55             ur define db me@dbi:Pg:dev Testing::Db1 # alternate for "Testing" (arbitrary) context
56             ur define db me@dbi:Pg:stage Staging::Db1 # alternate for "Staging" (arbitrary) context
57              
58             EOS
59 0     0 0   }
60              
61             sub data_source_module_pathname {
62 0     0 0   my $self = shift;
63 0           my $class_name = shift;
64              
65 0           my $ns_path = $self->namespace_path;
66              
67 0           my @ds_parts = split(/::/, $class_name);
68 0           shift @ds_parts; # Get rid of the namespace name
69              
70 0           my $filename = pop @ds_parts;
71 0           $filename .= '.pm';
72              
73 0           my $path = join('/', $ns_path, @ds_parts, $filename);
74 0           return $path;
75             }
76              
77             sub execute {
78 0     0     my $self = shift;
79              
80 0           my $namespace = $self->namespace_name;
81 0 0         unless ($namespace) {
82 0           $self->error_message("This command must be run from a namespace directory.");
83 0           return;
84             }
85              
86 0           my $uri = $self->uri;
87 0           my ($protocol,$driver,$login,$server,$owner) = ($uri =~ /^([^\:\W]+):(.*?):(.*@|)(.*?)(~.*|)$/);
88 0 0         unless ($protocol) {
89 0           $self->error_message("error parsing URI $uri\n" . 'expected dbi:$driver:$user@$server with optional trailing ~$namespace');
90 0           return;
91             }
92 0 0         unless ($protocol eq 'dbi') {
93 0           $self->error_message("currently only the 'dbi' protocol is supported with this command. Other data sources must be hand-written.");
94 0           return;
95             }
96 0 0         $login =~ s/\@$// if defined $login;
97 0 0         $owner =~ s/^~// if defined $owner;
98 0           $self->status_message("protocol: $protocol");
99 0           $self->status_message("driver: $driver");
100 0           $self->status_message("server: $server");
101 0           my $password;
102 0 0         if (defined $login) {
103 0 0         if ($login =~ /\//) {
104 0           ($login,$password) = split('/',$login);
105             }
106 0 0         $self->status_message("login: $login") if defined $login;
107 0 0         $self->status_message("password: $password") if defined $password;
108             }
109 0 0         $self->status_message("owner: $owner") if defined $owner;
110              
111             # Force an autoload of the namespace module
112 0           eval "use $namespace";
113 0 0         if ($@) {
114 0           $self->error_message("Can't load namespace $namespace: $@");
115 0           return;
116             }
117              
118 0           my $class_name = $self->namespace_name . '::DataSource::' . $self->name;
119 0           $self->_class_name($class_name);
120 0 0         my $c = eval { UR::DataSource->get($class_name) || $class_name->get() };
  0            
121 0 0         if ($c) {
122 0           $self->error_message("A data source named $class_name already exists\n");
123 0           return;
124             }
125              
126 0           my $src = "package $class_name;\nuse strict;\nuse warnings;\nuse $namespace;\n\n";
127 0           $src .= "class $class_name {\n";
128              
129 0           my $parent_ds_class = 'UR::DataSource::' . $driver; #$self->_data_source_sub_class_name();
130 0           $driver =~ s/mysql/MySQL/g;
131 0           my @parent_classes = ( $parent_ds_class );
132 0           push @parent_classes, 'UR::Singleton';
133 0           $src .= sprintf(" is => [ '%s' ],\n", join("', '", @parent_classes));
134 0           $src .= "};\n";
135              
136 0           my $module_body = $self->_resolve_module_body($class_name,$namespace,$driver,$server,$login);
137 0           $src .= "\n$module_body\n1;\n";
138              
139 0           my $module_path = $self->data_source_module_pathname($class_name);
140 0           my $fh = IO::File->new($module_path, O_WRONLY | O_CREAT | O_EXCL);
141 0 0         unless ($fh) {
142 0           $self->error_message("Can't open $module_path for writing: $!");
143 0           return;
144             }
145 0           $fh->print($src);
146 0           $fh->close();
147              
148 0           $self->status_message("A $class_name (" . join(',', @parent_classes) . ")\n");
149              
150             #TODO: call a method on the datasource to init the new file
151 0           my $method = '_post_module_written_' . lc($driver);
152 0           $self->$method($module_path,$server);
153              
154 0 0         unless (UR::Object::Type->use_module_with_namespace_constraints($class_name)) {
155             #if ($@) {
156 0           $self->error_message("Error in module $class_name!?: $@");
157 0           return;
158             }
159 0           my $ds = $class_name->get();
160 0 0         unless ($ds) {
161 0           $self->error_message("Failed to get data source for $class_name!");
162 0           return;
163             }
164 0           $self->_ds($ds);
165              
166 0 0         if ($self->_try_connect()) {
167 0           return 1;
168             } else {
169 0           return;
170             }
171             }
172            
173             sub _resolve_module_body {
174 0     0     my ($self,$class_name,$namespace,$driver,$server,$login,$owner) = @_;
175              
176 0   0       $owner ||= $login;
177              
178 0           my $src = <
179             sub driver { '$driver' };
180              
181             sub server { '$server' };
182              
183             EOS
184              
185             # TODO: key this off of a property on the datasource
186             # so datasource writers don't have to make a custom command here
187 0 0         if ($driver ne 'SQLite') {
188 0           $src .= "sub login { '$login' }\n";
189              
190 0           $src .= "sub auth { warn 'Set db password at ' . __LINE__ . ' in ' . __FILE__; return '' }\n";
191              
192 0           $src .= "sub owner { '$owner' }\n";
193             }
194              
195 0           return $src;
196             }
197              
198             sub _post_module_written_sqlite {
199 0     0     my ($self, $pathname, $server) = @_;
200              
201             # Create a new, empty DB if it dosen't exist yet
202 0 0         IO::File->new($server, O_WRONLY | O_CREAT) unless (-f $server);
203 0           $self->status_message("A $server (empty database schema)");
204              
205 0           $pathname =~ s/\.pm$/.sqlite3/;
206 0 0         unless ($pathname eq $server) {
207 0 0         symlink ($server, $pathname) or die "no symline $pathname for $server! $!";
208             }
209              
210 0           return 1;
211             }
212              
213             sub _post_module_written_pg {
214 0     0     my ($self, $pathname, $server) = @_;
215 0           return 1;
216             }
217              
218              
219             sub _post_module_written_oracle {
220 0     0     my ($self, $pathname, $server) = @_;
221 0           return 1;
222             }
223              
224             sub _post_module_written_mysql {
225 0     0     my ($self, $pathname, $server) = @_;
226 0           return 1;
227             }
228              
229             sub _post_module_written_file {
230 0     0     my ($self, $pathname, $server) = @_;
231 0           return 1;
232             }
233              
234             sub _post_module_written_filemux {
235 0     0     my ($self, $pathname, $server) = @_;
236 0           return 1;
237             }
238              
239              
240              
241             sub _try_connect {
242 0     0     my $self = shift;
243 0           $self->status_message(" ...connecting...");
244              
245 0           my $ds = $self->_ds;
246 0           my $dbh = $ds->get_default_handle();
247 0 0         if ($dbh) {
248 0           $self->status_message(" ....ok\n");
249 0           return 1;
250             } else {
251 0           $self->error_message(" ERROR: " . $ds->error_message);
252 0           return;
253             }
254             }
255              
256              
257             1;
258