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