blob: ec158f6832de581e677dbf27c064747fc7d136ef [file] [log] [blame]
Bram Moolenaare2f98b92006-03-29 21:18:24 +00001" Vim completion script
2" Language: SQL
3" Maintainer: David Fishburn <fishburn@ianywhere.com>
Bram Moolenaareb3593b2006-04-22 22:33:57 +00004" Version: 3.0
5" Last Change: Thu Apr 20 2006 8:47:12 PM
Bram Moolenaare2f98b92006-03-29 21:18:24 +00006
7" Set completion with CTRL-X CTRL-O to autoloaded function.
8" This check is in place in case this script is
9" sourced directly instead of using the autoload feature.
10if exists('&omnifunc')
11 " Do not set the option if already set since this
12 " results in an E117 warning.
13 if &omnifunc == ""
14 setlocal omnifunc=sqlcomplete#Complete
15 endif
16endif
17
18if exists('g:loaded_sql_completion')
19 finish
20endif
Bram Moolenaareb3593b2006-04-22 22:33:57 +000021let g:loaded_sql_completion = 30
Bram Moolenaare2f98b92006-03-29 21:18:24 +000022
23" Maintains filename of dictionary
Bram Moolenaar910f66f2006-04-05 20:41:53 +000024let s:sql_file_table = ""
25let s:sql_file_procedure = ""
26let s:sql_file_view = ""
Bram Moolenaare2f98b92006-03-29 21:18:24 +000027
28" Define various arrays to be used for caching
Bram Moolenaar910f66f2006-04-05 20:41:53 +000029let s:tbl_name = []
30let s:tbl_alias = []
31let s:tbl_cols = []
32let s:syn_list = []
33let s:syn_value = []
Bram Moolenaare2f98b92006-03-29 21:18:24 +000034
35" Used in conjunction with the syntaxcomplete plugin
Bram Moolenaar910f66f2006-04-05 20:41:53 +000036let s:save_inc = ""
37let s:save_exc = ""
Bram Moolenaare2f98b92006-03-29 21:18:24 +000038if exists('g:omni_syntax_group_include_sql')
39 let s:save_inc = g:omni_syntax_group_include_sql
40endif
41if exists('g:omni_syntax_group_exclude_sql')
42 let s:save_exc = g:omni_syntax_group_exclude_sql
43endif
44
45" Used with the column list
Bram Moolenaar910f66f2006-04-05 20:41:53 +000046let s:save_prev_table = ""
Bram Moolenaare2f98b92006-03-29 21:18:24 +000047
48" Default the option to verify table alias
49if !exists('g:omni_sql_use_tbl_alias')
50 let g:omni_sql_use_tbl_alias = 'a'
51endif
Bram Moolenaar910f66f2006-04-05 20:41:53 +000052" Default syntax items to precache
53if !exists('g:omni_sql_precache_syntax_groups')
54 let g:omni_sql_precache_syntax_groups = [
55 \ 'syntax',
56 \ 'sqlKeyword',
57 \ 'sqlFunction',
58 \ 'sqlOption',
59 \ 'sqlType',
60 \ 'sqlStatement'
61 \ ]
62endif
Bram Moolenaareb3593b2006-04-22 22:33:57 +000063" Set ignorecase to the ftplugin standard
64if !exists('g:omni_sql_ignorecase')
65 let g:omni_sql_ignorecase = &ignorecase
66endif
67" During table completion, should the table list also
68" include the owner name
69if !exists('g:omni_sql_include_owner')
70 let g:omni_sql_include_owner = 0
71 if exists('g:loaded_dbext')
72 if g:loaded_dbext >= 300
73 " New to dbext 3.00, by default the table lists include the owner
74 " name of the table. This is used when determining how much of
75 " whatever has been typed should be replaced as part of the
76 " code replacement.
77 let g:omni_sql_include_owner = 1
78 endif
79 endif
80endif
Bram Moolenaare2f98b92006-03-29 21:18:24 +000081
82" This function is used for the 'omnifunc' option.
83function! sqlcomplete#Complete(findstart, base)
84
85 " Default to table name completion
86 let compl_type = 'table'
87 " Allow maps to specify what type of object completion they want
88 if exists('b:sql_compl_type')
89 let compl_type = b:sql_compl_type
90 endif
91
Bram Moolenaar910f66f2006-04-05 20:41:53 +000092 " First pass through this function determines how much of the line should
93 " be replaced by whatever is chosen from the completion list
Bram Moolenaare2f98b92006-03-29 21:18:24 +000094 if a:findstart
95 " Locate the start of the item, including "."
96 let line = getline('.')
97 let start = col('.') - 1
98 let lastword = -1
99 while start > 0
100 if line[start - 1] =~ '\w'
101 let start -= 1
Bram Moolenaareb3593b2006-04-22 22:33:57 +0000102 elseif line[start - 1] =~ '\.' &&
103 \ compl_type =~ 'column\|table\|view\|procedure'
104 " If lastword has already been set for column completion
105 " break from the loop, since we do not also want to pickup
106 " a table name if it was also supplied.
107 if lastword != -1 && compl_type =~ 'column'
108 break
109 endif
110 " Assume we are looking for column completion
111 " column_type can be either 'column' or 'column_csv'
112 if lastword == -1 && compl_type =~ 'column'
Bram Moolenaare2f98b92006-03-29 21:18:24 +0000113 let lastword = start
114 endif
Bram Moolenaareb3593b2006-04-22 22:33:57 +0000115 " If omni_sql_include_owner = 0, do not include the table
116 " name as part of the substitution, so break here
117 if lastword == -1 &&
118 \ compl_type =~ 'table\|view\|procedure' &&
119 \ g:omni_sql_include_owner == 0
120 let lastword = start
121 break
122 endif
Bram Moolenaare2f98b92006-03-29 21:18:24 +0000123 let start -= 1
Bram Moolenaare2f98b92006-03-29 21:18:24 +0000124 else
125 break
126 endif
127 endwhile
128
129 " Return the column of the last word, which is going to be changed.
130 " Remember the text that comes before it in s:prepended.
131 if lastword == -1
132 let s:prepended = ''
133 return start
134 endif
135 let s:prepended = strpart(line, start, lastword - start)
136 return lastword
137 endif
138
Bram Moolenaar910f66f2006-04-05 20:41:53 +0000139 " Second pass through this function will determine what data to put inside
140 " of the completion list
141 " s:prepended is set by the first pass
Bram Moolenaare2f98b92006-03-29 21:18:24 +0000142 let base = s:prepended . a:base
143
Bram Moolenaar910f66f2006-04-05 20:41:53 +0000144 " Default the completion list to an empty list
Bram Moolenaare2f98b92006-03-29 21:18:24 +0000145 let compl_list = []
146
147 " Default to table name completion
148 let compl_type = 'table'
149 " Allow maps to specify what type of object completion they want
150 if exists('b:sql_compl_type')
151 let compl_type = b:sql_compl_type
152 unlet b:sql_compl_type
153 endif
154
155 if compl_type == 'tableReset'
156 let compl_type = 'table'
157 let base = ''
158 endif
159
160 if compl_type == 'table' ||
161 \ compl_type == 'procedure' ||
162 \ compl_type == 'view'
163
164 " This type of completion relies upon the dbext.vim plugin
165 if s:SQLCCheck4dbext() == -1
166 return []
167 endif
168
169 if s:sql_file_{compl_type} == ""
170 let compl_type = substitute(compl_type, '\w\+', '\u&', '')
171 let s:sql_file_{compl_type} = DB_getDictionaryName(compl_type)
172 endif
173 let s:sql_file_{compl_type} = DB_getDictionaryName(compl_type)
174 if s:sql_file_{compl_type} != ""
175 if filereadable(s:sql_file_{compl_type})
176 let compl_list = readfile(s:sql_file_{compl_type})
Bram Moolenaareb3593b2006-04-22 22:33:57 +0000177 " let dic_list = readfile(s:sql_file_{compl_type})
178 " if !empty(dic_list)
179 " for elem in dic_list
180 " let kind = (compl_type=='table'?'m':(compl_type=='procedure'?'f':'v'))
181 " let item = {'word':elem, 'menu':elem, 'kind':kind, 'info':compl_type}
182 " let compl_list += [item]
183 " endfor
184 " endif
Bram Moolenaare2f98b92006-03-29 21:18:24 +0000185 endif
186 endif
187 elseif compl_type == 'column'
188
189 " This type of completion relies upon the dbext.vim plugin
190 if s:SQLCCheck4dbext() == -1
191 return []
192 endif
193
194 if base == ""
195 " The last time we displayed a column list we stored
196 " the table name. If the user selects a column list
197 " without a table name of alias present, assume they want
198 " the previous column list displayed.
199 let base = s:save_prev_table
200 endif
201
202 if base != ""
203 let compl_list = s:SQLCGetColumns(base, '')
204 let s:save_prev_table = base
205 let base = ''
206 endif
207 elseif compl_type == 'column_csv'
208
209 " This type of completion relies upon the dbext.vim plugin
210 if s:SQLCCheck4dbext() == -1
211 return []
212 endif
213
214 if base == ""
215 " The last time we displayed a column list we stored
216 " the table name. If the user selects a column list
217 " without a table name of alias present, assume they want
218 " the previous column list displayed.
219 let base = s:save_prev_table
220 endif
221
222 if base != ""
223 let compl_list = s:SQLCGetColumns(base, 'csv')
224 let s:save_prev_table = base
225 " Join the column array into 1 single element array
226 " but make the columns column separated
227 let compl_list = [join(compl_list, ', ')]
228 let base = ''
229 endif
230 elseif compl_type == 'resetCache'
231 " Reset all cached items
232 let s:tbl_name = []
233 let s:tbl_alias = []
234 let s:tbl_cols = []
235 let s:syn_list = []
236 let s:syn_value = []
Bram Moolenaare2f98b92006-03-29 21:18:24 +0000237 else
Bram Moolenaar910f66f2006-04-05 20:41:53 +0000238 let compl_list = s:SQLCGetSyntaxList(compl_type)
Bram Moolenaare2f98b92006-03-29 21:18:24 +0000239 endif
240
241 if base != ''
242 " Filter the list based on the first few characters the user
243 " entered
Bram Moolenaareb3593b2006-04-22 22:33:57 +0000244 let expr = 'v:val '.(g:omni_sql_ignorecase==1?'=~?':'=~#').' "^'.base.'"'
245 let compl_list = filter(deepcopy(compl_list), expr)
Bram Moolenaare2f98b92006-03-29 21:18:24 +0000246 endif
247
Bram Moolenaar910f66f2006-04-05 20:41:53 +0000248 if exists('b:sql_compl_savefunc') && b:sql_compl_savefunc != ""
249 let &omnifunc = b:sql_compl_savefunc
250 endif
251
Bram Moolenaare2f98b92006-03-29 21:18:24 +0000252 return compl_list
253endfunc
254
255function! s:SQLCWarningMsg(msg)
256 echohl WarningMsg
257 echomsg a:msg
258 echohl None
259endfunction
260
261function! s:SQLCErrorMsg(msg)
262 echohl ErrorMsg
263 echomsg a:msg
264 echohl None
265endfunction
266
Bram Moolenaar910f66f2006-04-05 20:41:53 +0000267function! sqlcomplete#PreCacheSyntax(...)
268 let syn_group_arr = []
269 if a:0 > 0
270 let syn_group_arr = a:1
271 else
272 let syn_group_arr = g:omni_sql_precache_syntax_groups
273 endif
274 if !empty(syn_group_arr)
275 for group_name in syn_group_arr
276 call s:SQLCGetSyntaxList(group_name)
277 endfor
278 endif
279endfunction
280
281function! sqlcomplete#Map(type)
282 " Tell the SQL plugin what you want to complete
283 let b:sql_compl_type=a:type
284 " Record previous omnifunc, if the SQL completion
285 " is being used in conjunction with other filetype
286 " completion plugins
287 if &omnifunc != "" && &omnifunc != 'sqlcomplete#Complete'
288 " Record the previous omnifunc, the plugin
289 " will automatically set this back so that it
290 " does not interfere with other ftplugins settings
291 let b:sql_compl_savefunc=&omnifunc
292 endif
293 " Set the OMNI func for the SQL completion plugin
294 let &omnifunc='sqlcomplete#Complete'
295endfunction
296
297function! s:SQLCGetSyntaxList(syn_group)
298 let syn_group = a:syn_group
299 let compl_list = []
300
301 " Check if we have already cached the syntax list
302 let list_idx = index(s:syn_list, syn_group, 0, &ignorecase)
303 if list_idx > -1
304 " Return previously cached value
305 let compl_list = s:syn_value[list_idx]
306 else
307 " Request the syntax list items from the
308 " syntax completion plugin
309 if syn_group == 'syntax'
310 " Handle this special case. This allows the user
311 " to indicate they want all the syntax items available,
312 " so do not specify a specific include list.
313 let g:omni_syntax_group_include_sql = ''
314 else
315 " The user has specified a specific syntax group
316 let g:omni_syntax_group_include_sql = syn_group
317 endif
318 let g:omni_syntax_group_exclude_sql = ''
319 let syn_value = OmniSyntaxList()
320 let g:omni_syntax_group_include_sql = s:save_inc
321 let g:omni_syntax_group_exclude_sql = s:save_exc
322 " Cache these values for later use
323 let s:syn_list = add( s:syn_list, syn_group )
324 let s:syn_value = add( s:syn_value, syn_value )
325 let compl_list = syn_value
326 endif
327
328 return compl_list
329endfunction
330
Bram Moolenaare2f98b92006-03-29 21:18:24 +0000331function! s:SQLCCheck4dbext()
332 if !exists('g:loaded_dbext')
333 let msg = "The dbext plugin must be loaded for dynamic SQL completion"
334 call s:SQLCErrorMsg(msg)
335 " Leave time for the user to read the error message
336 :sleep 2
337 return -1
Bram Moolenaareb3593b2006-04-22 22:33:57 +0000338 elseif g:loaded_dbext < 300
339 let msg = "The dbext plugin must be at least version 3.00 " .
Bram Moolenaare2f98b92006-03-29 21:18:24 +0000340 \ " for dynamic SQL completion"
341 call s:SQLCErrorMsg(msg)
342 " Leave time for the user to read the error message
343 :sleep 2
344 return -1
345 endif
346 return 1
347endfunction
348
349function! s:SQLCAddAlias(table_name, table_alias, cols)
350 let table_name = a:table_name
351 let table_alias = a:table_alias
352 let cols = a:cols
353
354 if g:omni_sql_use_tbl_alias != 'n'
355 if table_alias == ''
356 if 'da' =~? g:omni_sql_use_tbl_alias
357 if table_name =~ '_'
358 " Treat _ as separators since people often use these
359 " for word separators
360 let save_keyword = &iskeyword
361 setlocal iskeyword-=_
362
363 " Get the first letter of each word
364 " [[:alpha:]] is used instead of \w
365 " to catch extended accented characters
366 "
367 let table_alias = substitute(
368 \ table_name,
369 \ '\<[[:alpha:]]\+\>_\?',
370 \ '\=strpart(submatch(0), 0, 1)',
371 \ 'g'
372 \ )
373 " Restore original value
374 let &iskeyword = save_keyword
375 elseif table_name =~ '\u\U'
376 let initials = substitute(
377 \ table_name, '\(\u\)\U*', '\1', 'g')
378 else
379 let table_alias = strpart(table_name, 0, 1)
380 endif
381 endif
382 endif
383 if table_alias != ''
384 " Following a word character, make sure there is a . and no spaces
385 let table_alias = substitute(table_alias, '\w\zs\.\?\s*$', '.', '')
386 if 'a' =~? g:omni_sql_use_tbl_alias && a:table_alias == ''
387 let table_alias = inputdialog("Enter table alias:", table_alias)
388 endif
389 endif
390 if table_alias != ''
391 let cols = substitute(cols, '\<\w', table_alias.'&', 'g')
392 endif
393 endif
394
395 return cols
396endfunction
397
398function! s:SQLCGetColumns(table_name, list_type)
399 let table_name = matchstr(a:table_name, '^\w\+')
400 let table_cols = []
401 let table_alias = ''
402 let move_to_top = 1
403
Bram Moolenaareb3593b2006-04-22 22:33:57 +0000404 if g:loaded_dbext >= 300
Bram Moolenaare2f98b92006-03-29 21:18:24 +0000405 let saveSettingAlias = DB_listOption('use_tbl_alias')
406 exec 'DBSetOption use_tbl_alias=n'
407 endif
408
409 " Check if we have already cached the column list for this table
410 " by its name
411 let list_idx = index(s:tbl_name, table_name, 0, &ignorecase)
412 if list_idx > -1
413 let table_cols = split(s:tbl_cols[list_idx])
414 else
415 " Check if we have already cached the column list for this table
416 " by its alias, assuming the table_name provided was actually
417 " the alias for the table instead
418 " select *
419 " from area a
420 " where a.
421 let list_idx = index(s:tbl_alias, table_name, 0, &ignorecase)
422 if list_idx > -1
423 let table_alias = table_name
424 let table_name = s:tbl_name[list_idx]
425 let table_cols = split(s:tbl_cols[list_idx])
426 endif
427 endif
428
429 " If we have not found a cached copy of the table
430 " And the table ends in a "." or we are looking for a column list
431 " if list_idx == -1 && (a:table_name =~ '\.' || b:sql_compl_type =~ 'column')
432 " if list_idx == -1 && (a:table_name =~ '\.' || a:list_type =~ 'csv')
433 if list_idx == -1
434 let saveY = @y
435 let saveSearch = @/
436 let saveWScan = &wrapscan
437 let curline = line(".")
438 let curcol = col(".")
439
440 " Do not let searchs wrap
441 setlocal nowrapscan
442 " If . was entered, look at the word just before the .
443 " We are looking for something like this:
444 " select *
445 " from customer c
446 " where c.
447 " So when . is pressed, we need to find 'c'
448 "
449
450 " Search backwards to the beginning of the statement
451 " and do NOT wrap
452 " exec 'silent! normal! v?\<\(select\|update\|delete\|;\)\>'."\n".'"yy'
453 exec 'silent! normal! ?\<\(select\|update\|delete\|;\)\>'."\n"
454
455 " Start characterwise visual mode
456 " Advance right one character
457 " Search foward until one of the following:
458 " 1. Another select/update/delete statement
459 " 2. A ; at the end of a line (the delimiter)
460 " 3. The end of the file (incase no delimiter)
461 " Yank the visually selected text into the "y register.
462 exec 'silent! normal! vl/\(\<select\>\|\<update\>\|\<delete\>\|;\s*$\|\%$\)'."\n".'"yy'
463
464 let query = @y
465 let query = substitute(query, "\n", ' ', 'g')
466 let found = 0
467
468 " if query =~? '^\(select\|update\|delete\)'
469 if query =~? '^\(select\)'
470 let found = 1
471 " \(\(\<\w\+\>\)\.\)\? -
472 " 'from.\{-}' - Starting at the from clause
473 " '\zs\(\(\<\w\+\>\)\.\)\?' - Get the owner name (optional)
474 " '\<\w\+\>\ze' - Get the table name
475 " '\s\+\<'.table_name.'\>' - Followed by the alias
476 " '\s*\.\@!.*' - Cannot be followed by a .
477 " '\(\<where\>\|$\)' - Must be followed by a WHERE clause
478 " '.*' - Exclude the rest of the line in the match
479 let table_name_new = matchstr(@y,
480 \ 'from.\{-}'.
481 \ '\zs\(\(\<\w\+\>\)\.\)\?'.
482 \ '\<\w\+\>\ze'.
483 \ '\s\+\%(as\s\+\)\?\<'.table_name.'\>'.
484 \ '\s*\.\@!.*'.
485 \ '\(\<where\>\|$\)'.
486 \ '.*'
487 \ )
488 if table_name_new != ''
489 let table_alias = table_name
490 let table_name = table_name_new
491
492 let list_idx = index(s:tbl_name, table_name, 0, &ignorecase)
493 if list_idx > -1
494 let table_cols = split(s:tbl_cols[list_idx])
495 let s:tbl_name[list_idx] = table_name
496 let s:tbl_alias[list_idx] = table_alias
497 else
498 let list_idx = index(s:tbl_alias, table_name, 0, &ignorecase)
499 if list_idx > -1
500 let table_cols = split(s:tbl_cols[list_idx])
501 let s:tbl_name[list_idx] = table_name
502 let s:tbl_alias[list_idx] = table_alias
503 endif
504 endif
505
506 endif
507 else
508 " Simply assume it is a table name provided with a . on the end
509 let found = 1
510 endif
511
512 let @y = saveY
513 let @/ = saveSearch
514 let &wrapscan = saveWScan
515
516 " Return to previous location
517 call cursor(curline, curcol)
518
519 if found == 0
Bram Moolenaareb3593b2006-04-22 22:33:57 +0000520 if g:loaded_dbext > 300
Bram Moolenaare2f98b92006-03-29 21:18:24 +0000521 exec 'DBSetOption use_tbl_alias='.saveSettingAlias
522 endif
523
524 " Not a SQL statement, do not display a list
525 return []
526 endif
527 endif
528
529 if empty(table_cols)
530 " Specify silent mode, no messages to the user (tbl, 1)
531 " Specify do not comma separate (tbl, 1, 1)
532 let table_cols_str = DB_getListColumn(table_name, 1, 1)
533
534 if table_cols_str != ""
535 let s:tbl_name = add( s:tbl_name, table_name )
536 let s:tbl_alias = add( s:tbl_alias, table_alias )
537 let s:tbl_cols = add( s:tbl_cols, table_cols_str )
538 let table_cols = split(table_cols_str)
539 endif
540
541 endif
542
Bram Moolenaareb3593b2006-04-22 22:33:57 +0000543 if g:loaded_dbext > 300
Bram Moolenaare2f98b92006-03-29 21:18:24 +0000544 exec 'DBSetOption use_tbl_alias='.saveSettingAlias
545 endif
546
547 if a:list_type == 'csv' && !empty(table_cols)
548 let cols = join(table_cols, ', ')
549 let cols = s:SQLCAddAlias(table_name, table_alias, cols)
550 let table_cols = [cols]
551 endif
552
553 return table_cols
554endfunction
555