1" Vim OMNI completion script for SQL
2" Language:    SQL
3" Maintainer:  David Fishburn <dfishburn dot vim at gmail dot com>
4" Version:     12.0
5" Last Change: 2012 Feb 08
6" Usage:       For detailed help
7"              ":help sql.txt"
8"              or ":help ft-sql-omni"
9"              or read $VIMRUNTIME/doc/sql.txt
10
11" History
12" Version 12.0
13"     - Partial column name completion did not work when a table
14"       name or table alias was provided (Jonas Enberg).
15"     - Improved the handling of column completion.  First we match any
16"       columns from a previous completion.  If not matches are found, we
17"       consider the partial name to be a table or table alias for the
18"       query and attempt to match on it.
19"
20" Version 11.0
21"     Added g:omni_sql_default_compl_type variable
22"         - You can specify which type of completion to default to
23"           when pressing <C-X><C-O>.  The entire list of available
24"           choices can be found in the calls to sqlcomplete#Map in:
25"               ftplugin/sql.vim
26"
27" Version 10.0
28"     Updated PreCacheSyntax()
29"         - Now returns a List of the syntax items it finds.
30"           This allows other plugins / scripts to use this list for their own
31"           purposes.  In this case XPTemplate can use them for a Choose list.
32"         - Verifies the parameters are the correct type and displays a
33"           warning if not.
34"         - Verifies the parameters are the correct type and displays a
35"           warning if not.
36"     Updated SQLCWarningMsg()
37"         - Prepends warning message with SQLComplete so you know who issued
38"           the warning.
39"     Updated SQLCErrorMsg()
40"         - Prepends error message with SQLComplete so you know who issued
41"           the error.
42"
43" Version 9.0
44"     This change removes some of the support for tables with spaces in their
45"     names in order to simplify the regexes used to pull out query table
46"     aliases for more robust table name and column name code completion.
47"     Full support for "table names with spaces" can be added in again
48"     after 7.3.
49"
50" Version 8.0
51"     Incorrectly re-executed the g:ftplugin_sql_omni_key_right and g:ftplugin_sql_omni_key_left
52"     when drilling in and out of a column list for a table.
53"
54" Version 7.0
55"     Better handling of object names
56"
57" Version 6.0
58"     Supports object names with spaces "my table name"
59"
60" Set completion with CTRL-X CTRL-O to autoloaded function.
61" This check is in place in case this script is
62" sourced directly instead of using the autoload feature.
63if exists('&omnifunc')
64    " Do not set the option if already set since this
65    " results in an E117 warning.
66    if &omnifunc == ""
67        setlocal omnifunc=sqlcomplete#Complete
68    endif
69endif
70
71if exists('g:loaded_sql_completion')
72    finish
73endif
74let g:loaded_sql_completion = 120
75
76" Maintains filename of dictionary
77let s:sql_file_table        = ""
78let s:sql_file_procedure    = ""
79let s:sql_file_view         = ""
80
81" Define various arrays to be used for caching
82let s:tbl_name              = []
83let s:tbl_alias             = []
84let s:tbl_cols              = []
85let s:syn_list              = []
86let s:syn_value             = []
87
88" Used in conjunction with the syntaxcomplete plugin
89let s:save_inc              = ""
90let s:save_exc              = ""
91if exists('g:omni_syntax_group_include_sql')
92    let s:save_inc = g:omni_syntax_group_include_sql
93endif
94if exists('g:omni_syntax_group_exclude_sql')
95    let s:save_exc = g:omni_syntax_group_exclude_sql
96endif
97
98" Used with the column list
99let s:save_prev_table       = ""
100
101" Default the option to verify table alias
102if !exists('g:omni_sql_use_tbl_alias')
103    let g:omni_sql_use_tbl_alias = 'a'
104endif
105" Default syntax items to precache
106if !exists('g:omni_sql_precache_syntax_groups')
107    let g:omni_sql_precache_syntax_groups = [
108                \ 'syntax',
109                \ 'sqlKeyword',
110                \ 'sqlFunction',
111                \ 'sqlOption',
112                \ 'sqlType',
113                \ 'sqlStatement'
114                \ ]
115endif
116" Set ignorecase to the ftplugin standard
117if !exists('g:omni_sql_ignorecase')
118    let g:omni_sql_ignorecase = &ignorecase
119endif
120" During table completion, should the table list also
121" include the owner name
122if !exists('g:omni_sql_include_owner')
123    let g:omni_sql_include_owner = 0
124    if exists('g:loaded_dbext')
125        if g:loaded_dbext >= 300
126            " New to dbext 3.00, by default the table lists include the owner
127            " name of the table.  This is used when determining how much of
128            " whatever has been typed should be replaced as part of the
129            " code replacement.
130            let g:omni_sql_include_owner = 1
131        endif
132    endif
133endif
134" Default type of completion used when <C-X><C-O> is pressed
135if !exists('g:omni_sql_default_compl_type')
136    let g:omni_sql_default_compl_type = 'table'
137endif
138
139" This function is used for the 'omnifunc' option.
140function! sqlcomplete#Complete(findstart, base)
141
142    " Default to table name completion
143    let compl_type = 'table'
144    " Allow maps to specify what type of object completion they want
145    if exists('b:sql_compl_type')
146        let compl_type = b:sql_compl_type
147    endif
148
149    " First pass through this function determines how much of the line should
150    " be replaced by whatever is chosen from the completion list
151    if a:findstart
152        " Locate the start of the item, including "."
153        let line     = getline('.')
154        let start    = col('.') - 1
155        let lastword = -1
156        let begindot = 0
157        " Check if the first character is a ".", for column completion
158        if line[start - 1] == '.'
159            let begindot = 1
160        endif
161        while start > 0
162            " Additional code was required to handle objects which
163            " can contain spaces like "my table name".
164            if line[start - 1] !~ '\(\w\|\.\)'
165                " If the previous character is not a period or word character
166                break
167            " elseif line[start - 1] =~ '\(\w\|\s\+\)'
168            "     let start -= 1
169            elseif line[start - 1] =~ '\w'
170                " If the previous character is word character continue back
171                let start -= 1
172            elseif line[start - 1] =~ '\.' &&
173                        \ compl_type =~ 'column\|table\|view\|procedure'
174                " If the previous character is a period and we are completing
175                " an object which can be specified with a period like this:
176                "     table_name.column_name
177                "     owner_name.table_name
178
179                " If lastword has already been set for column completion
180                " break from the loop, since we do not also want to pickup
181                " a table name if it was also supplied.
182                if lastword != -1 && compl_type == 'column'
183                    break
184                endif
185                " If column completion was specified stop at the "." if
186                " a . was specified, otherwise, replace all the way up
187                " to the owner name (if included).
188                if lastword == -1 && compl_type == 'column' && begindot == 1
189                    let lastword = start
190                endif
191                " If omni_sql_include_owner = 0, do not include the table
192                " name as part of the substitution, so break here
193                if lastword == -1 &&
194                            \ compl_type =~ 'table\|view\|procedure\column_csv' &&
195                            \ g:omni_sql_include_owner == 0
196                    let lastword = start
197                    break
198                endif
199                let start -= 1
200            else
201                break
202            endif
203        endwhile
204
205        " Return the column of the last word, which is going to be changed.
206        " Remember the text that comes before it in s:prepended.
207        if lastword == -1
208            let s:prepended = ''
209            return start
210        endif
211        let s:prepended = strpart(line, start, lastword - start)
212        return lastword
213    endif
214
215    " Second pass through this function will determine what data to put inside
216    " of the completion list
217    " s:prepended is set by the first pass
218    let base = s:prepended . a:base
219
220    " Default the completion list to an empty list
221    let compl_list = []
222
223    " Default to table name completion
224    let compl_type = g:omni_sql_default_compl_type
225    " Allow maps to specify what type of object completion they want
226    if exists('b:sql_compl_type')
227        let compl_type = b:sql_compl_type
228        unlet b:sql_compl_type
229    endif
230
231    if compl_type == 'tableReset'
232        let compl_type = 'table'
233        let base = ''
234    endif
235
236    if compl_type == 'table' ||
237                \ compl_type == 'procedure' ||
238                \ compl_type == 'view'
239
240        " This type of completion relies upon the dbext.vim plugin
241        if s:SQLCCheck4dbext() == -1
242            return []
243        endif
244
245        " Allow the user to override the dbext plugin to specify whether
246        " the owner/creator should be included in the list
247        if g:loaded_dbext >= 300
248            let saveSetting = DB_listOption('dict_show_owner')
249            exec 'DBSetOption dict_show_owner='.(g:omni_sql_include_owner==1?'1':'0')
250        endif
251
252        let compl_type_uc = substitute(compl_type, '\w\+', '\u&', '')
253        " Same call below, no need to do it twice
254        " if s:sql_file_{compl_type} == ""
255        "     let s:sql_file_{compl_type} = DB_getDictionaryName(compl_type_uc)
256        " endif
257        let s:sql_file_{compl_type} = DB_getDictionaryName(compl_type_uc)
258        if s:sql_file_{compl_type} != ""
259            if filereadable(s:sql_file_{compl_type})
260                let compl_list = readfile(s:sql_file_{compl_type})
261            endif
262        endif
263
264        if g:loaded_dbext > 300
265            exec 'DBSetOption dict_show_owner='.saveSetting
266        endif
267    elseif compl_type =~? 'column'
268
269        " This type of completion relies upon the dbext.vim plugin
270        if s:SQLCCheck4dbext() == -1
271            return []
272        endif
273
274        if base == ""
275            " The last time we displayed a column list we stored
276            " the table name.  If the user selects a column list
277            " without a table name of alias present, assume they want
278            " the previous column list displayed.
279            let base = s:save_prev_table
280        endif
281
282        let owner  = ''
283        let column = ''
284
285        if base =~ '\.'
286            " Check if the owner/creator has been specified
287            let owner  = matchstr( base, '^\zs.*\ze\..*\..*' )
288            let table  = matchstr( base, '^\(.*\.\)\?\zs.*\ze\..*' )
289            let column = matchstr( base, '.*\.\zs.*' )
290
291            " It is pretty well impossible to determine if the user
292            " has entered:
293            "    owner.table
294            "    table.column_prefix
295            " So there are a couple of things we can do to mitigate
296            " this issue.
297            "    1.  Check if the dbext plugin has the option turned
298            "        on to even allow owners
299            "    2.  Based on 1, if the user is showing a table list
300            "        and the DrillIntoTable (using <Right>) then
301            "        this will be owner.table.  In this case, we can
302            "        check to see the table.column exists in the
303            "        cached table list.  If it does, then we have
304            "        determined the user has actually chosen
305            "        owner.table, not table.column_prefix.
306            let found = -1
307            if g:omni_sql_include_owner == 1 && owner == ''
308                if filereadable(s:sql_file_table)
309                    let tbl_list = readfile(s:sql_file_table)
310                    let found    = index( tbl_list, ((table != '')?(table.'.'):'').column)
311                endif
312            endif
313            " If the table.column was found in the table list, we can safely assume
314            " the owner was not provided and shift the items appropriately.
315            " OR
316            " If the user has indicated not to use table owners at all and
317            " the base ends in a '.' we know they are not providing a column
318            " name, so we can shift the items appropriately.
319            " if found != -1 || (g:omni_sql_include_owner == 0 && base !~ '\.$')
320            "     let owner  = table
321            "     let table  = column
322            "     let column = ''
323            " endif
324        else
325            " If no "." was provided and the user asked for
326            " column level completion, first attempt the match
327            " on any previous column lists.  If the user asked
328            " for a list of columns comma separated, continue as usual.
329            if compl_type == 'column' && s:save_prev_table != ''
330                " The last time we displayed a column list we stored
331                " the table name.  If the user selects a column list
332                " without a table name of alias present, assume they want
333                " the previous column list displayed.
334                let table     = s:save_prev_table
335                let list_type = ''
336
337                let compl_list  = s:SQLCGetColumns(table, list_type)
338                if ! empty(compl_list)
339                    " If no column prefix has been provided and the table
340                    " name was provided, append it to each of the items
341                    " returned.
342                    let compl_list = filter(deepcopy(compl_list), 'v:val=~"^'.base.'"' )
343
344                    " If not empty, we have a match on columns
345                    " return the list
346                    if ! empty(compl_list)
347                        return compl_list
348                    endif
349                endif
350            endif
351            " Since no columns were found to match the base supplied
352            " assume the user is trying to complete the column list
353            " for a table (and or an alias to a table).
354            let table  = base
355        endif
356
357        " Get anything after the . and consider this the table name
358        " If an owner has been specified, then we must consider the
359        " base to be a partial column name
360        " let base  = matchstr( base, '^\(.*\.\)\?\zs.*' )
361
362        if table != ""
363            let s:save_prev_table = base
364            let list_type         = ''
365
366            if compl_type == 'column_csv'
367                " Return one array element, with a comma separated
368                " list of values instead of multiple array entries
369                " for each column in the table.
370                let list_type     = 'csv'
371            endif
372
373            let compl_list  = s:SQLCGetColumns(table, list_type)
374            if column != ''
375                " If no column prefix has been provided and the table
376                " name was provided, append it to each of the items
377                " returned.
378                let compl_list = map(compl_list, 'table.".".v:val')
379                if owner != ''
380                    " If an owner has been provided append it to each of the
381                    " items returned.
382                    let compl_list = map(compl_list, 'owner.".".v:val')
383                endif
384            else
385                let base = ''
386            endif
387
388            if compl_type == 'column_csv'
389                " Join the column array into 1 single element array
390                " but make the columns column separated
391                let compl_list        = [join(compl_list, ', ')]
392            endif
393        endif
394    elseif compl_type == 'resetCache'
395        " Reset all cached items
396        let s:tbl_name  = []
397        let s:tbl_alias = []
398        let s:tbl_cols  = []
399        let s:syn_list  = []
400        let s:syn_value = []
401
402        let msg = "All SQL cached items have been removed."
403        call s:SQLCWarningMsg(msg)
404        " Leave time for the user to read the error message
405        :sleep 2
406    else
407        let compl_list = s:SQLCGetSyntaxList(compl_type)
408    endif
409
410    if base != ''
411        " Filter the list based on the first few characters the user entered.
412        " Check if the text matches at the beginning
413        "         \\(^.base.'\\)
414        " or
415        " Match to a owner.table or alias.column type match
416        "         ^\\(\\w\\+\\.\\)\\?'.base.'\\)
417        " or
418        " Handle names with spaces "my table name"
419        "         "\\(^'.base.'\\|^\\(\\w\\+\\.\\)\\?'.base.'\\)"'
420        "
421        let expr = 'v:val '.(g:omni_sql_ignorecase==1?'=~?':'=~#').' "\\(^'.base.'\\|^\\(\\w\\+\\.\\)\\?'.base.'\\)"'
422        " let expr = 'v:val '.(g:omni_sql_ignorecase==1?'=~?':'=~#').' "\\(^'.base.'\\)"'
423        " let expr = 'v:val '.(g:omni_sql_ignorecase==1?'=~?':'=~#').' "\\(^'.base.'\\|\\(\\.\\)\\?'.base.'\\)"'
424        " let expr = 'v:val '.(g:omni_sql_ignorecase==1?'=~?':'=~#').' "\\(^'.base.'\\|\\([^.]*\\)\\?'.base.'\\)"'
425        let compl_list = filter(deepcopy(compl_list), expr)
426    endif
427
428    if exists('b:sql_compl_savefunc') && b:sql_compl_savefunc != ""
429        let &omnifunc = b:sql_compl_savefunc
430    endif
431
432    return compl_list
433endfunc
434
435function! sqlcomplete#PreCacheSyntax(...)
436    let syn_group_arr = []
437    let syn_items     = []
438
439    if a:0 > 0
440        if type(a:1) != 3
441            call s:SQLCWarningMsg("Parameter is not a list. Example:['syntaxGroup1', 'syntaxGroup2']")
442            return ''
443        endif
444        let syn_group_arr = a:1
445    else
446        let syn_group_arr = g:omni_sql_precache_syntax_groups
447    endif
448    " For each group specified in the list, precache all
449    " the sytnax items.
450    if !empty(syn_group_arr)
451        for group_name in syn_group_arr
452            let syn_items = extend( syn_items, s:SQLCGetSyntaxList(group_name) )
453        endfor
454    endif
455
456    return syn_items
457endfunction
458
459function! sqlcomplete#ResetCacheSyntax(...)
460    let syn_group_arr = []
461
462    if a:0 > 0
463        if type(a:1) != 3
464            call s:SQLCWarningMsg("Parameter is not a list. Example:['syntaxGroup1', 'syntaxGroup2']")
465            return ''
466        endif
467        let syn_group_arr = a:1
468    else
469        let syn_group_arr = g:omni_sql_precache_syntax_groups
470    endif
471    " For each group specified in the list, precache all
472    " the sytnax items.
473    if !empty(syn_group_arr)
474        for group_name in syn_group_arr
475            let list_idx = index(s:syn_list, group_name, 0, &ignorecase)
476            if list_idx > -1
477                " Remove from list of groups
478                call remove( s:syn_list, list_idx )
479                " Remove from list of keywords
480                call remove( s:syn_value, list_idx )
481            endif
482        endfor
483    endif
484endfunction
485
486function! sqlcomplete#Map(type)
487    " Tell the SQL plugin what you want to complete
488    let b:sql_compl_type=a:type
489    " Record previous omnifunc, if the SQL completion
490    " is being used in conjunction with other filetype
491    " completion plugins
492    if &omnifunc != "" && &omnifunc != 'sqlcomplete#Complete'
493        " Record the previous omnifunc, the plugin
494        " will automatically set this back so that it
495        " does not interfere with other ftplugins settings
496        let b:sql_compl_savefunc=&omnifunc
497    endif
498    " Set the OMNI func for the SQL completion plugin
499    let &omnifunc='sqlcomplete#Complete'
500endfunction
501
502function! sqlcomplete#DrillIntoTable()
503    " If the omni popup window is visible
504    if pumvisible()
505        call sqlcomplete#Map('column')
506        " C-Y, makes the currently highlighted entry active
507        " and trigger the omni popup to be redisplayed
508        call feedkeys("\<C-Y>\<C-X>\<C-O>", 'n')
509    else
510	" If the popup is not visible, simple perform the normal
511	" key behaviour.
512	" Must use exec since they key must be preceeded by "\"
513	" or feedkeys will simply push each character of the string
514	" rather than the "key press".
515        exec 'call feedkeys("\'.g:ftplugin_sql_omni_key_right.'", "n")'
516    endif
517    return ""
518endfunction
519
520function! sqlcomplete#DrillOutOfColumns()
521    " If the omni popup window is visible
522    if pumvisible()
523        call sqlcomplete#Map('tableReset')
524        " Trigger the omni popup to be redisplayed
525        call feedkeys("\<C-X>\<C-O>")
526    else
527	" If the popup is not visible, simple perform the normal
528	" key behaviour.
529	" Must use exec since they key must be preceeded by "\"
530	" or feedkeys will simply push each character of the string
531	" rather than the "key press".
532        exec 'call feedkeys("\'.g:ftplugin_sql_omni_key_left.'", "n")'
533    endif
534    return ""
535endfunction
536
537function! s:SQLCWarningMsg(msg)
538    echohl WarningMsg
539    echomsg 'SQLComplete:'.a:msg
540    echohl None
541endfunction
542
543function! s:SQLCErrorMsg(msg)
544    echohl ErrorMsg
545    echomsg 'SQLComplete:'.a:msg
546    echohl None
547endfunction
548
549function! s:SQLCGetSyntaxList(syn_group)
550    let syn_group  = a:syn_group
551    let compl_list = []
552
553    " Check if we have already cached the syntax list
554    let list_idx = index(s:syn_list, syn_group, 0, &ignorecase)
555    if list_idx > -1
556        " Return previously cached value
557        let compl_list = s:syn_value[list_idx]
558    else
559        " Request the syntax list items from the
560        " syntax completion plugin
561        if syn_group == 'syntax'
562            " Handle this special case.  This allows the user
563            " to indicate they want all the syntax items available,
564            " so do not specify a specific include list.
565            let g:omni_syntax_group_include_sql = ''
566        else
567            " The user has specified a specific syntax group
568            let g:omni_syntax_group_include_sql = syn_group
569        endif
570        let g:omni_syntax_group_exclude_sql = ''
571        let syn_value                       = syntaxcomplete#OmniSyntaxList()
572        let g:omni_syntax_group_include_sql = s:save_inc
573        let g:omni_syntax_group_exclude_sql = s:save_exc
574        " Cache these values for later use
575        let s:syn_list  = add( s:syn_list,  syn_group )
576        let s:syn_value = add( s:syn_value, syn_value )
577        let compl_list  = syn_value
578    endif
579
580    return compl_list
581endfunction
582
583function! s:SQLCCheck4dbext()
584    if !exists('g:loaded_dbext')
585        let msg = "The dbext plugin must be loaded for dynamic SQL completion"
586        call s:SQLCErrorMsg(msg)
587        " Leave time for the user to read the error message
588        :sleep 2
589        return -1
590    elseif g:loaded_dbext < 600
591        let msg = "The dbext plugin must be at least version 5.30 " .
592                    \ " for dynamic SQL completion"
593        call s:SQLCErrorMsg(msg)
594        " Leave time for the user to read the error message
595        :sleep 2
596        return -1
597    endif
598    return 1
599endfunction
600
601function! s:SQLCAddAlias(table_name, table_alias, cols)
602    " Strip off the owner if included
603    let table_name  = matchstr(a:table_name, '\%(.\{-}\.\)\?\zs\(.*\)' )
604    let table_alias = a:table_alias
605    let cols        = a:cols
606
607    if g:omni_sql_use_tbl_alias != 'n'
608        if table_alias == ''
609            if 'da' =~? g:omni_sql_use_tbl_alias
610                if table_name =~ '_'
611                    " Treat _ as separators since people often use these
612                    " for word separators
613                    let save_keyword = &iskeyword
614                    setlocal iskeyword-=_
615
616                    " Get the first letter of each word
617                    " [[:alpha:]] is used instead of \w
618                    " to catch extended accented characters
619                    "
620                    let table_alias = substitute(
621                                \ table_name,
622                                \ '\<[[:alpha:]]\+\>_\?',
623                                \ '\=strpart(submatch(0), 0, 1)',
624                                \ 'g'
625                                \ )
626                    " Restore original value
627                    let &iskeyword = save_keyword
628                elseif table_name =~ '\u\U'
629                    let table_alias = substitute(
630                                \ table_name, '\(\u\)\U*', '\1', 'g')
631                else
632                    let table_alias = strpart(table_name, 0, 1)
633                endif
634            endif
635        endif
636        if table_alias != ''
637            " Following a word character, make sure there is a . and no spaces
638            let table_alias = substitute(table_alias, '\w\zs\.\?\s*$', '.', '')
639            if 'a' =~? g:omni_sql_use_tbl_alias && a:table_alias == ''
640                let table_alias = inputdialog("Enter table alias:", table_alias)
641            endif
642        endif
643        if table_alias != ''
644            let cols = substitute(cols, '\<\w', table_alias.'&', 'g')
645        endif
646    endif
647
648    return cols
649endfunction
650
651function! s:SQLCGetObjectOwner(object)
652    " The owner regex matches a word at the start of the string which is
653    " followed by a dot, but doesn't include the dot in the result.
654    " ^           - from beginning of line
655    " \("\|\[\)\? - ignore any quotes
656    " \zs         - start the match now
657    " .\{-}       - get owner name
658    " \ze         - end the match
659    " \("\|\[\)\? - ignore any quotes
660    " \.          - must by followed by a .
661    " let owner = matchstr( a:object, '^\s*\zs.*\ze\.' )
662    let owner = matchstr( a:object, '^\("\|\[\)\?\zs\.\{-}\ze\("\|\]\)\?\.' )
663    return owner
664endfunction
665
666function! s:SQLCGetColumns(table_name, list_type)
667    " Check if the table name was provided as part of the column name
668    let table_name   = matchstr(a:table_name, '^["\[\]a-zA-Z0-9_ ]\+\ze\.\?')
669    let table_cols   = []
670    let table_alias  = ''
671    let move_to_top  = 1
672
673    let table_name   = substitute(table_name, '\s*\(.\{-}\)\s*$', '\1', 'g')
674
675    " If the table name was given as:
676    "     where c.
677    let table_name   = substitute(table_name, '^\c\(WHERE\|AND\|OR\)\s\+', '', '')
678    if g:loaded_dbext >= 300
679        let saveSettingAlias = DB_listOption('use_tbl_alias')
680        exec 'DBSetOption use_tbl_alias=n'
681    endif
682
683    let table_name_stripped = substitute(table_name, '["\[\]]*', '', 'g')
684
685    " Check if we have already cached the column list for this table
686    " by its name
687    let list_idx = index(s:tbl_name, table_name_stripped, 0, &ignorecase)
688    if list_idx > -1
689        let table_cols = split(s:tbl_cols[list_idx], '\n')
690    else
691        " Check if we have already cached the column list for this table
692        " by its alias, assuming the table_name provided was actually
693        " the alias for the table instead
694        "     select *
695        "       from area a
696        "      where a.
697        let list_idx = index(s:tbl_alias, table_name_stripped, 0, &ignorecase)
698        if list_idx > -1
699            let table_alias = table_name_stripped
700            let table_name  = s:tbl_name[list_idx]
701            let table_cols  = split(s:tbl_cols[list_idx], '\n')
702        endif
703    endif
704
705    " If we have not found a cached copy of the table
706    " And the table ends in a "." or we are looking for a column list
707    " if list_idx == -1 && (a:table_name =~ '\.' || b:sql_compl_type =~ 'column')
708    " if list_idx == -1 && (a:table_name =~ '\.' || a:list_type =~ 'csv')
709    if list_idx == -1
710         let saveY      = @y
711         let saveSearch = @/
712         let saveWScan  = &wrapscan
713         let curline    = line(".")
714         let curcol     = col(".")
715
716         " Do not let searchs wrap
717         setlocal nowrapscan
718         " If . was entered, look at the word just before the .
719         " We are looking for something like this:
720         "    select *
721         "      from customer c
722         "     where c.
723         " So when . is pressed, we need to find 'c'
724         "
725
726         " Search backwards to the beginning of the statement
727         " and do NOT wrap
728         " exec 'silent! normal! v?\<\(select\|update\|delete\|;\)\>'."\n".'"yy'
729         exec 'silent! normal! ?\<\c\(select\|update\|delete\|;\)\>'."\n"
730
731         " Start characterwise visual mode
732         " Advance right one character
733         " Search foward until one of the following:
734         "     1.  Another select/update/delete statement
735         "     2.  A ; at the end of a line (the delimiter)
736         "     3.  The end of the file (incase no delimiter)
737         " Yank the visually selected text into the "y register.
738         exec 'silent! normal! vl/\c\(\<select\>\|\<update\>\|\<delete\>\|;\s*$\|\%$\)'."\n".'"yy'
739
740         let query = @y
741         let query = substitute(query, "\n", ' ', 'g')
742         let found = 0
743
744         " if query =~? '^\c\(select\)'
745         if query =~? '^\(select\|update\|delete\)'
746             let found = 1
747             "  \(\(\<\w\+\>\)\.\)\?   -
748             " '\c\(from\|join\|,\).\{-}'  - Starting at the from clause (case insensitive)
749             " '\zs\(\(\<\w\+\>\)\.\)\?' - Get the owner name (optional)
750             " '\<\w\+\>\ze' - Get the table name
751             " '\s\+\<'.table_name.'\>' - Followed by the alias
752             " '\s*\.\@!.*'  - Cannot be followed by a .
753             " '\(\<where\>\|$\)' - Must be followed by a WHERE clause
754             " '.*'  - Exclude the rest of the line in the match
755             " let table_name_new = matchstr(@y,
756             "             \ '\c\(from\|join\|,\).\{-}'.
757             "             \ '\zs\(\("\|\[\)\?.\{-}\("\|\]\)\.\)\?'.
758             "             \ '\("\|\[\)\?.\{-}\("\|\]\)\?\ze'.
759             "             \ '\s\+\%(as\s\+\)\?\<'.
760             "             \ matchstr(table_name, '.\{-}\ze\.\?$').
761             "             \ '\>'.
762             "             \ '\s*\.\@!.*'.
763             "             \ '\(\<where\>\|$\)'.
764             "             \ '.*'
765             "             \ )
766             "
767             "
768             " ''\c\(\<from\>\|\<join\>\|,\)\s*'  - Starting at the from clause (case insensitive)
769             " '\zs\(\("\|\[\)\?\w\+\("\|\]\)\?\.\)\?' - Get the owner name (optional)
770             " '\("\|\[\)\?\w\+\("\|\]\)\?\ze' - Get the table name
771             " '\s\+\%(as\s\+\)\?\<'.matchstr(table_name, '.\{-}\ze\.\?$').'\>' - Followed by the alias
772             " '\s*\.\@!.*'  - Cannot be followed by a .
773             " '\(\<where\>\|$\)' - Must be followed by a WHERE clause
774             " '.*'  - Exclude the rest of the line in the match
775             let table_name_new = matchstr(@y,
776                         \ '\c\(\<from\>\|\<join\>\|,\)\s*'.
777                         \ '\zs\(\("\|\[\)\?\w\+\("\|\]\)\?\.\)\?'.
778                         \ '\("\|\[\)\?\w\+\("\|\]\)\?\ze'.
779                         \ '\s\+\%(as\s\+\)\?\<'.
780                         \ matchstr(table_name, '.\{-}\ze\.\?$').
781                         \ '\>'.
782                         \ '\s*\.\@!.*'.
783                         \ '\(\<where\>\|$\)'.
784                         \ '.*'
785                         \ )
786
787             if table_name_new != ''
788                 let table_alias = table_name
789                 let table_name  = matchstr( table_name_new, '^\(.*\.\)\?\zs.*\ze' )
790
791                 let list_idx = index(s:tbl_name, table_name, 0, &ignorecase)
792                 if list_idx > -1
793                     let table_cols  = split(s:tbl_cols[list_idx])
794                     let s:tbl_name[list_idx]  = table_name
795                     let s:tbl_alias[list_idx] = table_alias
796                 else
797                     let list_idx = index(s:tbl_alias, table_name, 0, &ignorecase)
798                     if list_idx > -1
799                         let table_cols = split(s:tbl_cols[list_idx])
800                         let s:tbl_name[list_idx]  = table_name
801                         let s:tbl_alias[list_idx] = table_alias
802                     endif
803                 endif
804
805             endif
806         else
807             " Simply assume it is a table name provided with a . on the end
808             let found = 1
809         endif
810
811         let @y        = saveY
812         let @/        = saveSearch
813         let &wrapscan = saveWScan
814
815         " Return to previous location
816         call cursor(curline, curcol)
817
818         if found == 0
819             if g:loaded_dbext > 300
820                 exec 'DBSetOption use_tbl_alias='.saveSettingAlias
821             endif
822
823             " Not a SQL statement, do not display a list
824             return []
825         endif
826    endif
827
828    if empty(table_cols)
829        " Specify silent mode, no messages to the user (tbl, 1)
830        " Specify do not comma separate (tbl, 1, 1)
831        let table_cols_str = DB_getListColumn(table_name, 1, 1)
832
833        if table_cols_str != ""
834            let s:tbl_name  = add( s:tbl_name,  table_name )
835            let s:tbl_alias = add( s:tbl_alias, table_alias )
836            let s:tbl_cols  = add( s:tbl_cols,  table_cols_str )
837            let table_cols  = split(table_cols_str, '\n')
838        endif
839
840    endif
841
842    if g:loaded_dbext > 300
843        exec 'DBSetOption use_tbl_alias='.saveSettingAlias
844    endif
845
846    " If the user has asked for a comma separate list of column
847    " values, ask the user if they want to prepend each column
848    " with a tablename alias.
849    if a:list_type == 'csv' && !empty(table_cols)
850        let cols       = join(table_cols, ', ')
851        let cols       = s:SQLCAddAlias(table_name, table_alias, cols)
852        let table_cols = [cols]
853    endif
854
855    return table_cols
856endfunction
857