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