File Coverage

blib/lib/DBI/Shell/Completion.pm
Criterion Covered Total %
statement 13 174 7.4
branch 2 88 2.2
condition 2 12 16.6
subroutine 4 13 30.7
pod 0 8 0.0
total 21 295 7.1


line stmt bran cond sub pod time code
1             package DBI::Shell::Completion;
2             # vim:ts=4:sw=4:ai:aw:nowrapscan
3              
4             our $VERSION = '11.97'; # VERSION
5              
6 4     4   30 use strict;
  4         8  
  4         138  
7 4     4   20 use Carp;
  4         8  
  4         8376  
8              
9             my ($loa, @matches, @tables, @table_list, $tbl_nm, $term, $history);
10              
11              
12             sub init {
13 4     4 0 11 my ($class, $sh, @args) = @_;
14 4   33     23 $class = ref $class || $class;
15 4         90 $loa = {
16             'catalogs' => undef,
17             'commands' => undef,
18             'sql' => [ sort qw(
19             select insert update delete
20             alter grant revoke
21             from where order by desc asc
22             join exists spool
23             set min max avg count
24             into values
25             ) ],
26             'select_func' => [ sort qw(
27             count(*) min max avg as distinct unique
28             ) ],
29             'schemas' => undef,
30             'system' => undef,
31             'tables' => undef,
32             'ntables' => undef, # Maintain a list of columns by table.
33             'sql_keywords' => undef,
34             'users' => undef,
35             'views' => undef,
36             'term' => undef, # Maintain a reference to the term type.
37             'history' => '.dbish_history',
38             'command_prefix' => undef,
39             'columns' => undef,
40             };
41              
42            
43              
44             # Modify the history location to use the users home directory, if
45             # available.
46             # TODO: Change this to be less unix more perl
47             $loa->{history} = $sh->{home_dir} . '/' . $loa->{history}
48 4 50 33     37 if (exists $sh->{home_dir} and defined $sh->{home_dir});
49              
50 4         33 $sh->log( "commandline history written to $loa->{history}" );
51              
52 4         23 my $pi = bless $loa, $class;
53              
54             # return if term is not defined.
55 4 50       30 return unless $sh->{term};
56              
57 0           $term = $sh->{term};
58              
59 0           my $attribs = $term->Attribs();
60 0           $attribs->{history_length} = '500';
61              
62 0           $pi->{term} = \$sh->{term};
63 0           $pi->{dbh} = \$sh->{dbh};
64 0           $pi->{command_prefix} = \$sh->{command_prefix};
65              
66 0 0         if ($term->ReadLine eq "Term::ReadLine::Gnu") {
67 0           print "Using Term::ReadLine::Gnu\n";
68              
69             # Only source the current drivers Completion, if exists.
70 0           $sh->{completion} = $pi;
71              
72             # Define the completion function.
73             my $ssc = sub {
74 0     0     return $pi->sql_shell_completion(@_);
75 0           };
76 0           $attribs->{attempted_completion_function} = $ssc;
77              
78             # read in the history file.
79 0 0         if(-e $pi->{history}) {
80             $sh->log ("History file $pi->{history} not restored!" )
81 0 0         unless($term->ReadHistory($pi->{history}));
82             } else {
83 0           print "Creating ${history} to store your command line history\n";
84 0 0         open(HISTORY, "> $pi->{history}")
85             or $sh->log ("Could not create $pi->{history}: $!");
86 0           close(HISTORY);
87             }
88              
89             }
90              
91 0           return $pi;
92             }
93              
94             # sub load_completion {
95             # my $cpi = shift;
96             # my $sh = shift;
97             # my @pi;
98             # foreach my $where (qw(DBI/Shell/Completion DBI_Shell_Completion)) {
99             # my $mod = $where; $mod =~ s!/!::!g; #/ so vim see the syn correctly
100             # my @dir = map { -d "$_/$where" ? ("$_/$where") : () } @INC;
101             # foreach my $dir (@dir) {
102             # opendir DIR, $dir or warn "Unable to read $dir: $!\n";
103             # push @pi, map { s/\.pm$//; "${mod}::$_" } grep { /\.pm$/ }
104             # readdir DIR;
105             # closedir DIR;
106             # }
107             # }
108             # my $driver = $sh->{data_source};
109             # # print STDERR join( " ", @pi, $driver, "\n");
110             # foreach my $pi (sort @pi) {
111             # #local $DBI::Shell::SHELL = $sh; # publish the current shell
112             # eval qq{ use $pi };
113             # $sh->alert("Unable to load $pi: $@") if $@;
114             # }
115             # # plug-ins should remove options they recognise from (localized) @ARGV
116             # # by calling Getopt::Long::GetOptions (which is already in pass_through mode).
117             # foreach my $pi (@pi) {
118             # #local *ARGV = $sh->{unhandled_options};
119             # $pi->init($sh);
120             # }
121             # }
122              
123             sub populate {
124 0     0 0   my $sh = shift;
125 0           my $list = shift;
126              
127 0 0         return $loa unless $list;
128 0 0         return undef unless exists $loa->{$list};
129              
130             # print ( "$list populate ...", join " ", @_, "\n" );
131              
132 0 0         if (@_) { # User provided a list of values.
133 0           $loa->{$list} = [ @_ ];
134             }
135 0           return $loa->{$list};
136             }
137              
138             # Attempt to complete on the contents of TEXT. START and END bound
139             # the region of rl_line_buffer that contains the word to complete.
140             # TEXT is the word to complete. We can use the entire contents of
141             # rl_line_buffer in case we want to do some simple parsing. Return
142             # the array of matches, or NULL if there aren't any.
143             sub sql_shell_completion {
144 0     0 0   my $sh = shift;
145 0           my ($text, $line, $start, $end) = @_;
146              
147 0           my @matches = ();
148              
149 0           undef $tbl_nm;
150              
151             # Notes for future development. The $line is the complete line,
152             # start is where the text begins, end where text ends (looks like word
153             # boundies). I need to attempt to determine where I'm in the line, and
154             # what was the last key word given.
155              
156             # print STDERR "text:$text: line:$line: start:$start: end:$end:\n";
157 0           my $cmd_p = ${$sh->{command_prefix}};
  0            
158              
159             # Load the keywords.
160 0 0         unless (defined $loa->{sql_keywords}) {
161 0           eval {
162             # Not all drivers support the get_info function yet, so we
163             # need a fall back plan.
164 0           my $key_words = ${$sh->{dbh}}->get_info( 'SQL_KEYWORDS' );
  0            
165 0 0         die unless (defined $key_words);
166 0           my @key_words = split( /\s+/, $key_words);
167 0 0         die unless (@key_words); # Keywords not supported by driver, default
168             $sh->populate( q{sql_keywords}, @key_words )
169 0 0         unless (defined $loa->{sql_keywords});
170             };
171              
172 0 0         if($@) {
173 0           $sh->populate( q{sql_keywords}, @{$sh->{sql}} );
  0            
174             }
175             }
176              
177 0 0         unless (defined $loa->{columns}) {
178 0           eval {
179 0           my $sth = ${$sh->{dbh}}->column_info( undef, undef, undef, undef );
  0            
180 0 0         die unless $sth; # column_info not supported by all drivers.
181 0           my (%catalogs, %schemas, %tables, %columns);
182 0           while ( my $row = $sth->fetchrow_arrayref ) {
183 0 0         $catalogs{$row->[0]}++ if defined $row->[0];
184 0 0         $schemas{$row->[1]}++ if defined $row->[1];
185 0 0         $tables{$row->[2]}++ if defined $row->[2];
186 0 0         $columns{$row->[3]}++ if defined $row->[3];
187              
188 0           push ( @{$loa->{ntables}->{$row->[2]}}, $row->[3] );
  0            
189             }
190 0           push( @{$loa->{catalogs}}, sort keys %catalogs );
  0            
191 0           push( @{$loa->{schemas}}, sort keys %schemas );
  0            
192 0           push( @{$loa->{columns}}, sort keys %columns );
  0            
193             };
194              
195 0           push( @{$loa->{columns}}, @{$sh->{select_func}} );
  0            
  0            
196             }
197              
198             # print "line: $line - $cmd_p\n" if $line;
199             # Begin by loading all the key words, if available.
200 0 0         if ( $start == 0 ) {
    0          
    0          
    0          
201             # SQL_KEYWORDS
202             @matches =
203 0           ${$sh->{term}}->completion_matches($text,
  0            
204             \&sql_keywords_gen);
205             }
206             # If the last word is "from" attempt to match a schema or table name.
207             elsif(
208             $line=~ m/
209             \bfrom(?:\s*)?(?:['"])?$
210             |
211             \bfrom(?:\s*)(?:['"])?(?:[\w.]+)
212             |
213             \binsert\s+into(?:\s+)?$
214             |
215             \binsert\s+into\s+(?:['"])?(?:\w+|[\w+.]|\w+\.\w+)$
216             |
217             \bupdate(?:\s*)?(?:['"])?(?:\w+)?$
218             |
219             ^${cmd_p}desc(?:\s*)?(?:['"])?(?:\w+)?
220             /xi
221             ) {
222             $sh->populate(q{tables},
223 0 0         ${$sh->{dbh}}->tables) unless($loa->{tables});
  0            
224 0           @matches = ${$sh->{term}}->completion_matches($text, \&table_generator);
  0            
225             # |
226             # ^${cmd_p}desc(?:\s+)(?:['"])?\w+?$
227             }
228             # If we find a select on the line display a column list.
229             elsif( $line=~ m/select\s+?$|select\s+\w+?$/i ) {
230 0           @matches = ${$sh->{term}}->completion_matches($text,
  0            
231             \&column_generator);
232             }
233             elsif( $line=~ m/
234             ^insert\s+
235             into\s+
236             ((?:\w+|\w+\.\w+))\s+?\( # )
237             /xi ) {
238 0           $tbl_nm = $1;
239 0 0         unless( exists $loa->{ntables}->{$tbl_nm} ) {
240 0           eval {
241 0           my $sth = ${$sh->{dbh}}->column_info( undef, undef, $tbl_nm, undef );
  0            
242 0 0         die unless $sth; # column_info not supported by all drivers.
243 0           push( @{$loa->{ntables}->{$tbl_nm}},
244 0           @{$sth->fetchall_arrayref( [3] )} );
  0            
245            
246             };
247              
248 0 0         if ($@) {
249             # Column Info not supported, do it the hard way.
250             {
251 0           local (${$sh->{dbh}}->{PrintError},
  0            
252 0           ${$sh->{dbh}}->{RaiseError});
  0            
253 0           ${$sh->{dbh}}->{PrintError} = 0;
  0            
254 0           ${$sh->{dbh}}->{RaiseError} = 0;
  0            
255 0           my $sth = ${$sh->{dbh}}->prepare( qq{select * from $tbl_nm where 1 = 2} );
  0            
256 0           $sth->execute;
257              
258 0 0         unless($sth->err) {
259 0           push( @{$loa->{ntables}->{$tbl_nm}}, @{$sth->{NAME}} );
  0            
  0            
260             }
261 0           $sth->finish;
262             }
263             }
264              
265             }
266              
267 0           @matches = ${$sh->{term}}->completion_matches($text,
  0            
268             \&col_tab_gen );
269             }
270             else {
271             # match commands for now.
272             @matches =
273 0           ${$sh->{term}}->completion_matches($text, \&sql_keywords_gen);
  0            
274             }
275              
276 0           return @matches;
277             }
278              
279             # Generator function for command completion. STATE lets us know
280             # whether to start from scratch; without any state (i.e. STATE == 0),
281             # then we start at the top of the list.
282              
283             ## Term::ReadLine::Gnu has list_completion_function similar with this
284             ## function. I defined new one to be compared with original C version.
285             {
286             my $list_index;
287             my (@name, @columns, @tables);
288              
289             sub column_generator {
290 0     0 0   my ($text, $state) = @_;
291              
292             # If this is a new word to complete, initialize now. This
293             # includes saving the length of TEXT for efficiency, and
294             # initializing the index variable to 0.
295 0 0         unless ($state) {
296 0           $list_index = 0;
297 0           @columns = @{$loa->{columns}};
  0            
298             }
299              
300             # Return the next name which partially matches from the
301             # command list.
302 0           while ($list_index <= $#columns) {
303 0           $list_index++;
304 0 0         return $columns[$list_index - 1]
305             if ($columns[$list_index - 1] =~ /^$text/i);
306             }
307             # If no names matched, then return NULL.
308 0           return undef;
309             }
310              
311             sub col_tab_gen {
312 0     0 0   my ($text, $state) = @_;
313              
314             # Just return undef for now.
315              
316             # If this is a new word to complete, initialize now. This
317             # includes saving the length of TEXT for efficiency, and
318             # initializing the index variable to 0.
319 0 0         unless ($state) {
320 0           $list_index = 0;
321 0 0         if (exists $loa->{ntables}->{$tbl_nm}) {
322 0           @columns = @{$loa->{ntables}->{$tbl_nm}};
  0            
323             }
324             else {
325 0           @columns = @{$loa->{columns}};
  0            
326             }
327             }
328              
329             # Return the next name which partially matches from the
330             # command list.
331 0           while ($list_index <= $#columns) {
332 0           $list_index++;
333 0 0         return $columns[$list_index - 1]
334             if ($columns[$list_index - 1] =~ /^$text/i);
335             }
336             # If no names matched, then return NULL.
337 0           return undef;
338             }
339              
340             sub sql_generator {
341 0     0 0   my ($text, $state) = @_;
342              
343             # If this is a new word to complete, initialize now. This
344             # includes saving the length of TEXT for efficiency, and
345             # initializing the index variable to 0.
346 0 0         unless ($state) {
347 0           $list_index = 0;
348 0           @name = @{$loa->{sql}};
  0            
349             }
350              
351             # Return the next name which partially matches from the
352             # command list.
353 0           while ($list_index <= $#name) {
354 0           $list_index++;
355 0 0         return $name[$list_index - 1]
356             if ($name[$list_index - 1] =~ /^$text/i);
357             }
358             # If no names matched, then return NULL.
359 0           return undef;
360             }
361              
362             sub sql_keywords_gen {
363 0     0 0   my ($text, $state) = @_;
364              
365             # If this is a new word to complete, initialize now. This
366             # includes saving the length of TEXT for efficiency, and
367             # initializing the index variable to 0.
368 0 0         unless ($state) {
369 0           $list_index = 0;
370 0           @name = @{$loa->{sql_keywords}};
  0            
371             }
372              
373             # Return the next name which partially matches from the
374             # command list.
375 0           while ($list_index <= $#name) {
376 0           $list_index++;
377 0 0         return $name[$list_index - 1]
378             if ($name[$list_index - 1] =~ /^$text/i);
379             }
380             # If no names matched, then return NULL.
381 0           return undef;
382             }
383             }
384              
385             {
386             my $list_index;
387              
388             sub table_generator {
389 0     0 0   my ($text, $state) = @_;
390              
391             # If this is a new table to complete, initialize now. This
392             # includes saving the length of TEXT for efficiency, and
393             # initializing the index variable to 0.
394 0 0         unless ($state) {
395 0           $list_index = 0;
396 0           @tables = @{$loa->{tables}};
  0            
397             }
398              
399             # Return the next name which partially matches from the
400             # command list.
401 0           while ($list_index <= $#tables) {
402 0           $list_index++;
403 0 0         return $tables[$list_index - 1]
404             if ($tables[$list_index - 1] =~ /^$text/i);
405             }
406             # If no names matched, then return NULL.
407 0           return undef;
408             }
409             }
410              
411             DESTROY {
412 0     0     my $sh = shift;
413             # term is store as a package variable.
414 0 0 0       if ($term && $term->ReadLine eq "Term::ReadLine::Gnu") {
415 0 0 0       if($term && $term->history_total_bytes()) {
416 0           my $history = $sh->{completion}->{history};
417 0 0         if ($history) {
418 0 0         unless($term->WriteHistory($history)) {
419 0           carp ("Could not write history file $history to history_file}. ");
420             }
421             }
422             }
423             }
424              
425 0           $term = undef; $sh->{term} = undef;
  0            
426             }
427              
428       4     END { }
429              
430             1;
431             __END__