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