neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

sqlcomplete.vim (39275B)


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