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