diff --git a/autoload/dbext.vim b/autoload/dbext.vim index 99bdd56..2ac4758 100644 --- a/autoload/dbext.vim +++ b/autoload/dbext.vim @@ -1,11 +1,11 @@ -" dbext.vim - Commn Database Utility +" dbext.vim - Common Database Utility " Copyright (C) 2002-16, Peter Bagyinszki, David Fishburn " --------------------------------------------------------------- -" Version: 23.00 +" Version: 26.00 " Maintainer: David Fishburn " Authors: Peter Bagyinszki " David Fishburn -" Last Modified: 2015 Dec 30 +" Last Modified: 2017 Oct 10 " Based On: sqlplus.vim (author: Jamis Buck) " Created: 2002-05-24 " Homepage: http://vim.sourceforge.net/script.php?script_id=356 @@ -38,7 +38,7 @@ if v:version < 700 echomsg "dbext: Version 4.00 or higher requires Vim7. Version 3.50 can stil be used with Vim6." finish endif -let g:loaded_dbext_auto = 2300 +let g:loaded_dbext_auto = 2600 " Turn on support for line continuations when creating the script let s:cpo_save = &cpo @@ -67,6 +67,10 @@ let s:dbext_buffer_last = -1 " let s:dbext_prev_winnr = 0 " let s:dbext_prev_bufnr = 0 " }}} +let s:dbext_job_support = 0 +if has('channel') && has('job') && has('timers') + let s:dbext_job_support = 1 +endif " Build internal lists {{{ function! dbext#DB_buildLists() @@ -190,6 +194,11 @@ function! dbext#DB_buildLists() call add(s:config_params_mv, 'strip_into') call add(s:config_params_mv, 'strip_at_variables') call add(s:config_params_mv, 'passwd_use_secret') + call add(s:config_params_mv, 'job_enable') + call add(s:config_params_mv, 'job_status_update_ms') + call add(s:config_params_mv, 'job_show_msgs') + call add(s:config_params_mv, 'job_pipe_regex') + call add(s:config_params_mv, 'job_quote_regex') " Script parameters let s:script_params_mv = [] @@ -910,6 +919,11 @@ function! s:DB_getDefault(name) elseif a:name ==# "strip_into" |return (exists("g:dbext_default_strip_into")?g:dbext_default_strip_into.'':'1') elseif a:name ==# "strip_at_variables" |return (exists("g:dbext_default_strip_at_variables")?g:dbext_default_strip_at_variables.'':'0') elseif a:name ==# "passwd_use_secret" |return (exists("g:dbext_default_passwd_use_secret")?g:dbext_default_passwd_use_secret.'':'0') + elseif a:name ==# "job_enable" |return (exists("g:dbext_default_job_enable")?g:dbext_default_job_enable.'':'1') + elseif a:name ==# "job_status_update_ms" |return (exists("g:dbext_default_job_status_update_ms")?g:dbext_default_job_status_update_ms.'':'2000') + elseif a:name ==# "job_show_msgs" |return (exists("g:dbext_default_job_show_msgs")?g:dbext_default_job_show_msgs.'':'1') + elseif a:name ==# "job_pipe_regex" |return (exists("g:dbext_default_job_pipe_regex")?g:dbext_default_job_pipe_regex.'':'\%(@\|<\)') + elseif a:name ==# "job_quote_regex" |return (exists("g:dbext_default_job_quote_regex")?g:dbext_default_job_quote_regex.'':'\%("\)') elseif a:name ==# "ASA_bin" |return (exists("g:dbext_default_ASA_bin")?g:dbext_default_ASA_bin.'':'dbisql') elseif a:name ==# "ASA_cmd_terminator" |return (exists("g:dbext_default_ASA_cmd_terminator")?g:dbext_default_ASA_cmd_terminator.'':';') elseif a:name ==# "ASA_cmd_options" |return (exists("g:dbext_default_ASA_cmd_options")?g:dbext_default_ASA_cmd_options.'':'-nogui') @@ -975,7 +989,7 @@ function! s:DB_getDefault(name) \ "set flush off\n" . \ "set colsep \" \"\n" . \ "set tab off\n\n") - elseif a:name ==# "ORA_cmd_options" |return (exists("g:dbext_default_ORA_cmd_options")?g:dbext_default_ORA_cmd_options.'':"-S") + elseif a:name ==# "ORA_cmd_options" |return (exists("g:dbext_default_ORA_cmd_options")?g:dbext_default_ORA_cmd_options.'':"-L -S") elseif a:name ==# "ORA_cmd_terminator" |return (exists("g:dbext_default_ORA_cmd_terminator")?g:dbext_default_ORA_cmd_terminator.'':";") elseif a:name ==# "ORA_SQL_Top_pat" |return (exists("g:dbext_default_ORA_SQL_Top_pat")?g:dbext_default_ORA_SQL_Top_pat.'':'\(.*\)') elseif a:name ==# "ORA_SQL_Top_sub" |return (exists("g:dbext_default_ORA_SQL_Top_sub")?g:dbext_default_ORA_SQL_Top_sub.'':'SELECT * FROM (\1) WHERE rownum <= @dbext_topX ') @@ -1143,6 +1157,11 @@ function! s:DB_resetGlobalParameters() silent! let g: redir END + " ans 2022-04-09: prevent dbext barking on us because FuzzyFinder + " remembers something in his global variable FuzzyFinderMode about db_ext!!! (something + " of my recent searches apparently) + let @a = substitute(@a, "\nFuzzyFin.*\n","\n",'') + if @a =~ 'db_ext' call s:DB_warningMsg("You have used a previous version of db_ext. ") call s:DB_warningMsg("The configuration parameters have changed. ") @@ -1244,9 +1263,9 @@ function! s:DB_resetBufferParameters(use_defaults) " Try to be smarter about setting the defaults. " DB_resetBufferParameters can be called recursively. " After it completes it sets "buffer_defaulted". - " What can happen when you first launch Vim is there - " are no connection params setup. - " Then you attempt to use dbext for the first time. + " What can happen when you first launch Vim if there + " are no connection params setup " and then you attempt + " to use dbext for the first time. " So the following happens: " 1. We default all buffer parameters to blanks (buffer_defaulted = 0) " 2. We default all buffer parameters to any defaults specified @@ -1281,6 +1300,8 @@ function! s:DB_resetBufferParameters(use_defaults) else let retval = s:DB_set(param, value) endif + else + let value = s:DB_promptForParameters(param) endif endif endfor @@ -1814,7 +1835,7 @@ function! s:DB_ASA_execSql(str) else let links = "" endif - let cmd = dbext_bin . ' ' . dbext#DB_getWType("cmd_options") . ' ' . + let cmd = dbext#DB_getWType("cmd_options") . ' ' . \ s:DB_option('-onerror ', dbext#DB_getWType("on_error"), ' ') . \ ' -c "' . \ s:DB_option('uid=', s:DB_get("user"), ';') . @@ -1830,7 +1851,7 @@ function! s:DB_ASA_execSql(str) endif let cmd = cmd . '" ' . \ ' read ' . s:dbext_tempfile - let result = s:DB_runCmd(cmd, output, "") + let result = s:DB_runCmdJobSupport(dbext_bin, cmd, output, "") return result endfunction @@ -2078,7 +2099,7 @@ function! s:DB_IQ_execSql(str) else let links = "" endif - let cmd = dbext_bin . ' ' . dbext#DB_getWType("cmd_options") . ' ' . + let cmd = dbext#DB_getWType("cmd_options") . ' ' . \ s:DB_option('-onerror ', dbext#DB_getWType("on_error"), ' ') . \ ' -c "' . \ s:DB_option('uid=', s:DB_get("user"), ';') . @@ -2094,7 +2115,7 @@ function! s:DB_IQ_execSql(str) endif let cmd = cmd . '" ' . \ ' read ' . s:dbext_tempfile - let result = s:DB_runCmd(cmd, output, "") + let result = s:DB_runCmdJobSupport(dbext_bin, cmd, output, "") return result endfunction @@ -2320,7 +2341,7 @@ function! s:DB_ULTRALITE_execSql(str) let dbext_bin = s:DB_fullPath2Bin(dbext#DB_getWType("bin")) - let cmd = dbext_bin . ' ' . dbext#DB_getWType("cmd_options") . ' ' . + let cmd = dbext#DB_getWType("cmd_options") . ' ' . \ s:DB_option('-onerror ', dbext#DB_getWType("on_error"), ' ') . \ ' -c "' . \ s:DB_option('uid=', s:DB_get("user"), ';') . @@ -2330,7 +2351,7 @@ function! s:DB_ULTRALITE_execSql(str) \ s:DB_option('', dbext#DB_getWTypeDefault("extra"), '') let cmd = cmd . '" ' . \ ' read ' . s:dbext_tempfile - let result = s:DB_runCmd(cmd, output, "") + let result = s:DB_runCmdJobSupport(dbext_bin, cmd, output, "") return result endfunction @@ -2449,7 +2470,7 @@ function! s:DB_ASE_execSql(str) let dbext_bin = s:DB_fullPath2Bin(dbext#DB_getWType("bin")) - let cmd = dbext_bin . ' ' . + let cmd = '' . \ s:DB_option('', dbext#DB_getWType("cmd_options"), ' ') . \ s:DB_option('-U ', s:DB_get("user"), ' ') . \ s:DB_option('-P ', s:DB_get("passwd"), ' ') . @@ -2459,7 +2480,7 @@ function! s:DB_ASE_execSql(str) \ s:DB_option('', dbext#DB_getWTypeDefault("extra"), '') . \ ' -i ' . s:dbext_tempfile - let result = s:DB_runCmd(cmd, output, "") + let result = s:DB_runCmdJobSupport(dbext_bin, cmd, output, "") return result endfunction @@ -2606,14 +2627,14 @@ function! s:DB_CRATE_execSql(str) let dbext_bin = s:DB_fullPath2Bin(dbext#DB_getWType("bin")) - let cmd = dbext_bin . ' ' . dbext#DB_getWType("cmd_options") + let cmd = dbext#DB_getWType("cmd_options") let cmd = cmd . \ s:DB_option('--hosts ', s:DB_get("host") . ":" . s:DB_get("port"), '') . \ ' < ' . s:dbext_tempfile " This fix requires PR with adds support for piping under " windows (crate/crash#99 - https://github.com/crate/crash/pull/99). "\ ' -c "' . join(readfile(s:dbext_tempfile)) . '"' - let result = s:DB_runCmd(cmd, output, "") + let result = s:DB_runCmdJobSupport(dbext_bin, cmd, output, "") return result endfunction @@ -2754,9 +2775,9 @@ function! s:DB_DB2_execSql(str) " In batch files I used the following " -c close when done " -w wait until command finishes - " -i don’t spawn a new cmd window - " -t don’t change the window title - " db2cmd -c -w -i –t “db2 -s -t ; -v -f dave.sql” + " -i do not spawn a new cmd window + " -t do not change the window title + " db2cmd -c -w -i -t "db2 -s -t ; -v -f dave.sql" " To see command line options " cd IBM\SQLLIB\BIN " db2cmd -w -i @@ -2786,7 +2807,7 @@ function! s:DB_DB2_execSql(str) let dbext_bin = s:DB_fullPath2Bin(dbext#DB_getWType("bin")) - let cmd = dbext_bin . ' ' . dbext#DB_getWType("cmd_options") . ' ' + let cmd = dbext#DB_getWType("cmd_options") . ' ' if s:DB_get("user") != "" let cmd = cmd . ' -a ' . s:DB_get("user") . '/' . \ s:DB_get("passwd") . ' ' @@ -2837,14 +2858,14 @@ function! s:DB_DB2_execSql(str) let dbext_bin = s:DB_fullPath2Bin(dbext#DB_getWType("db2cmd_bin")) - let cmd = dbext_bin . ' ' . dbext#DB_getWType("db2cmd_cmd_options") + let cmd = dbext#DB_getWType("db2cmd_cmd_options") let cmd = cmd . ' ' . s:DB_option('', dbext#DB_getWTypeDefault("extra"), ' ') . \ ((dbext#DB_getWType("cmd_terminator")!='')?(s:DB_option('-t', dbext#DB_getWType("cmd_terminator"), ' ')):' ') . \ '-f ' . s:dbext_tempfile endif - let result = s:DB_runCmd(cmd, output, "") + let result = s:DB_runCmdJobSupport(dbext_bin, cmd, output, "") return result endfunction @@ -3020,12 +3041,12 @@ function! s:DB_INGRES_execSql(str) let dbext_bin = s:DB_fullPath2Bin(dbext#DB_getWType("bin")) - let cmd = dbext_bin . ' ' . + let cmd = '' . \ s:DB_option('', dbext#DB_getWTypeDefault("extra"), ' ') . \ s:DB_option('-S ', s:DB_get("dbname"), ' ') . \ s:DB_option('', dbext#DB_getWType("cmd_options"), ' ') . \ ' < ' . s:dbext_tempfile - let result = s:DB_runCmd(cmd, output, "") + let result = s:DB_runCmdJobSupport(dbext_bin, cmd, output, "") return result endfunction @@ -3107,14 +3128,14 @@ function! s:DB_INTERBASE_execSql(str) let dbext_bin = s:DB_fullPath2Bin(dbext#DB_getWType("bin")) - let cmd = dbext_bin . ' ' . + let cmd = '' . \ s:DB_option('-username ', s:DB_get("user"), ' ') . \ s:DB_option('-password ', s:DB_get("passwd"), ' ') . \ s:DB_option('', dbext#DB_getWType("cmd_options"), ' ') . \ s:DB_option('', dbext#DB_getWTypeDefault("extra"), ' ') . \ '-input ' . s:dbext_tempfile . \ s:DB_option(' ', s:DB_get("dbname"), '') - let result = s:DB_runCmd(cmd, output, "") + let result = s:DB_runCmdJobSupport(dbext_bin, cmd, output, "") return result endfunction @@ -3196,7 +3217,7 @@ function! s:DB_MYSQL_execSql(str) let dbext_bin = s:DB_fullPath2Bin(dbext#DB_getWType("bin")) - let cmd = dbext_bin . ' ' . dbext#DB_getWType("cmd_options") + let cmd = dbext#DB_getWType("cmd_options") let cmd = cmd . \ s:DB_option(' -u ', s:DB_get("user"), '') . \ s:DB_option(' -p', s:DB_get("passwd"), '') . @@ -3206,7 +3227,7 @@ function! s:DB_MYSQL_execSql(str) \ s:DB_option(' ', dbext#DB_getWTypeDefault("extra"), '') . \ ' < ' . s:dbext_tempfile " \ s:DB_option(' ', '-t', '') . - let result = s:DB_runCmd(cmd, output, "") + let result = s:DB_runCmdJobSupport(dbext_bin, cmd, output, "") return result endfunction @@ -3373,12 +3394,12 @@ function! s:DB_SQLITE_execSql(str) let dbext_bin = s:DB_fullPath2Bin(dbext#DB_getWType("bin")) - let cmd = dbext_bin . ' ' . dbext#DB_getWType("cmd_options") + let cmd = dbext#DB_getWType("cmd_options") let cmd = cmd . \ s:DB_option(' ', dbext#DB_getWTypeDefault("extra"), '') . \ s:DB_option(' ', s:DB_get("dbname"), '') . \ ' < ' . s:dbext_tempfile - let result = s:DB_runCmd(cmd, output, "") + let result = s:DB_runCmdJobSupport(dbext_bin, cmd, output, "") return result endfunction @@ -3506,14 +3527,13 @@ function! s:DB_ORA_execSql(str) let dbext_bin = s:DB_fullPath2Bin(dbext#DB_getWType("bin")) - let cmd = dbext_bin . - \ ' ' . dbext#DB_getWType("cmd_options") . + let cmd = dbext#DB_getWType("cmd_options") . \ s:DB_option(' "', s:DB_get("user"), '') . \ s:DB_option('/', s:DB_get("passwd"), '') . \ s:DB_option('@', s:DB_get("srvname"), '') . \ s:DB_option(' ', dbext#DB_getWTypeDefault("extra"), '') . \ '" @' . s:dbext_tempfile - let result = s:DB_runCmd(cmd, output, "") + let result = s:DB_runCmdJobSupport(dbext_bin, cmd, output, "") return result endfunction @@ -3529,15 +3549,15 @@ endfunction function! s:DB_ORA_getListTable(table_prefix) let owner = toupper(s:DB_getObjectOwner(a:table_prefix)) let table_name = toupper(s:DB_getObjectName(a:table_prefix)) - let query = "select table_name, owner, tablespace_name ". - \ " from ALL_ALL_TABLES ". - \ " where table_name LIKE '".table_name."%' " + let query = "SELECT table_name, owner, tablespace_name ". + \ " FROM ALL_ALL_TABLES ". + \ " WHERE UPPER(table_name) LIKE UPPER('".table_name."%') " if strlen(owner) > 0 let query = query . - \ " and owner = '".owner."' " + \ " AND UPPER(owner) = UPPER('".owner."') " endif let query = query . - \ " order by table_name" + \ " ORDER BY table_name" return s:DB_ORA_execSql( query ) endfunction @@ -3551,59 +3571,60 @@ function! s:DB_ORA_getListProcedure(proc_prefix) if !empty(owner) if !empty(pkg_name) " schema.package.procedure - let query = "select procedure_name object_name, owner ||'.'|| object_name owner ". - \ " from all_procedures ". - \ " where object_type = 'PACKAGE' ". - \ " and procedure_name LIKE '".obj_name."%' ". - \ " and owner = '".owner."' and object_name = '".pkg_name."'" - else " schema.procedureORpackage or package.procedure - let query = "select object_name, owner ". - \ " from all_objects ". - \ " where object_type IN ('PROCEDURE', 'PACKAGE', 'FUNCTION') ". - \ " and object_name LIKE '".obj_name."%' ". - \ " and owner = '".owner."'". + let query = "SELECT procedure_name object_name, owner ||'.'|| object_name owner ". + \ " FROM all_procedures ". + \ " WHERE object_type = 'PACKAGE' ". + \ " AND UPPER(procedure_name) LIKE UPPER('".obj_name."%') ". + \ " AND UPPER(owner) = UPPER('".owner."') " + \ " AND UPPER(object_name) = UPPER('".pkg_name."') " + else " schema.procedure OR package OR package.procedure + let query = "SELECT object_name, owner ". + \ " FROM all_objects ". + \ " WHERE object_type IN ('PROCEDURE', 'PACKAGE', 'FUNCTION') ". + \ " AND UPPER(object_name) LIKE UPPER('".obj_name."%') ". + \ " AND UPPER(owner) = UPPER('".owner."') ". \ " UNION ALL ". - \ "select procedure_name, object_name ". - \ " from all_procedures ". - \ " where object_type = 'PACKAGE' ". - \ " and object_name = '".owner."'". - \ " and procedure_name LIKE '".obj_name."%'" + \ "SELECT procedure_name, object_name ". + \ " FROM all_procedures ". + \ " WHERE object_type = 'PACKAGE' ". + \ " AND UPPER(object_name) = UPPER('".owner."') ". + \ " AND UPPER(procedure_name) LIKE UPPER('".obj_name."%') " endif else " just a name - let query = "select object_name, owner ". - \ " from all_objects ". - \ " where object_type IN ('PROCEDURE', 'PACKAGE', 'FUNCTION') " . - \ " and object_name LIKE '".obj_name."%' " + let query = "SELECT object_name, owner ". + \ " FROM all_objects ". + \ " WHERE object_type IN ('PROCEDURE', 'PACKAGE', 'FUNCTION') " . + \ " AND UPPER(object_name) LIKE UPPER('".obj_name."%') " endif - let query .= " order by 1" + let query .= " ORDER BY 1" return s:DB_ORA_execSql( query ) endfunction function! s:DB_ORA_getListView(view_prefix) let owner = toupper(s:DB_getObjectOwner(a:view_prefix)) let obj_name = toupper(s:DB_getObjectName(a:view_prefix)) - let query = "select view_name, owner ". - \ " from ALL_VIEWS ". - \ " where view_name LIKE '".obj_name."%' " + let query = "SELECT view_name, owner ". + \ " FROM ALL_VIEWS ". + \ " WHERE UPPER(view_name) LIKE UPPER('".obj_name."%') " if strlen(owner) > 0 let query = query . - \ " and owner = '".owner."' " + \ " AND UPPER(owner) = UPPER('".owner."') " endif - let query .= " order by view_name" + let query .= " ORDER BY view_name" return s:DB_ORA_execSql( query ) endfunction function! s:DB_ORA_getListColumn(table_name) "{{{ let owner = toupper(s:DB_getObjectOwner(a:table_name)) let table_name = toupper(s:DB_getObjectName(a:table_name)) - let query = "select column_name ". - \ " from ALL_TAB_COLUMNS ". - \ " where table_name = '".table_name."' " + let query = "SELECT column_name ". + \ " FROM ALL_TAB_COLUMNS ". + \ " WHERE UPPER(table_name) = UPPER('".table_name."') " if !empty(owner) - let query .= " and owner = '".owner."' " + let query .= " AND UPPER(owner) = UPPER('".owner."') " endif - let query .= " order by column_id" + let query .= " ORDER BY column_id" let result = s:DB_ORA_execSql( query ) return s:DB_ORA_stripHeaderFooter(result) endfunction "}}} @@ -3717,15 +3738,14 @@ function! s:DB_PGSQL_execSql(str) let dbext_bin = s:DB_fullPath2Bin(dbext#DB_getWType("bin")) - let cmd = dbext_bin . ' ' . - \ s:DB_option('', dbext#DB_getWType("cmd_options"), ' ') . + let cmd = s:DB_option('', dbext#DB_getWType("cmd_options"), ' ') . \ s:DB_option('-d ', s:DB_get("dbname"), ' ') . \ s:DB_option('-U ', s:DB_get("user"), ' ') . \ s:DB_option('-h ', s:DB_get("host"), ' ') . \ s:DB_option('-p ', s:DB_get("port"), ' ') . \ s:DB_option(' ', dbext#DB_getWTypeDefault("extra"), '') . \ ' -q -f ' . s:dbext_tempfile - let result = s:DB_runCmd(cmd, output, "") + let result = s:DB_runCmdJobSupport(dbext_bin, cmd, output, "") return result endfunction @@ -3919,8 +3939,8 @@ function! s:DB_RDB_execSql(str) "{{{ let dbext_bin = s:DB_fullPath2Bin(dbext#DB_getWType("bin")) - let cmd = dbext_bin . ' @' . s:dbext_tempfile - let result = s:DB_runCmd(cmd, output, "") + let cmd = '@' . s:dbext_tempfile + let result = s:DB_runCmdJobSupport(dbext_bin, cmd, output, "") return result endfunction "}}} @@ -4089,7 +4109,7 @@ function! s:DB_SQLSRV_execSql(str) let dbext_bin = s:DB_fullPath2Bin(dbext#DB_getWType("bin")) - let cmd = dbext_bin . ' ' . dbext#DB_getWType("cmd_options") + let cmd = dbext#DB_getWType("cmd_options") if has("win32") && s:DB_get("integratedlogin") == 1 let cmd = cmd . ' -E' @@ -4103,7 +4123,7 @@ function! s:DB_SQLSRV_execSql(str) \ s:DB_option(' -d ', s:DB_get("dbname"), ' ') . \ s:DB_option(' ', dbext#DB_getWTypeDefault("extra"), '') . \ ' -i ' . s:dbext_tempfile - let result = s:DB_runCmd(cmd, output, "") + let result = s:DB_runCmdJobSupport(dbext_bin, cmd, output, "") return result endfunction @@ -4242,14 +4262,14 @@ function! s:DB_FIREBIRD_execSql(str) let dbext_bin = s:DB_fullPath2Bin(dbext#DB_getWType("bin")) - let cmd = dbext_bin . ' ' . dbext#DB_getWType("cmd_options") + let cmd = dbext#DB_getWType("cmd_options") let cmd = cmd . \ s:DB_option(' -u ', s:DB_get("user"), '') . \ s:DB_option(' -p ', s:DB_get("passwd"), '') . \ s:DB_option(' ', s:DB_get("dbname"), '') . \ s:DB_option(' ', dbext#DB_getWTypeDefault("extra"), '') . \ ' < ' . s:dbext_tempfile - let result = s:DB_runCmd(cmd, output, "") + let result = s:DB_runCmdJobSupport(dbext_bin, cmd, output, "") return result endfunction @@ -4373,14 +4393,14 @@ function! s:DB_HANA_execSql(str) let dbext_bin = s:DB_fullPath2Bin(dbext#DB_getWType("bin")) - let cmd = dbext_bin . ' ' . dbext#DB_getWType("cmd_options") . ' ' . + let cmd = dbext#DB_getWType("cmd_options") . ' ' . \ s:DB_option('-n ', s:DB_get("host"), ' ') . \ s:DB_option('-u ', s:DB_get("user"), ' ') . \ s:DB_option('-p ', s:DB_get("passwd"), ' ') . \ s:DB_option('', dbext#DB_getWTypeDefault("extra"), '') let cmd = cmd . ' ' . \ ' -I ' . s:dbext_tempfile - let result = s:DB_runCmd(cmd, output, "") + let result = s:DB_runCmdJobSupport(dbext_bin, cmd, output, "") return result endfunction @@ -4752,6 +4772,9 @@ function! s:DB_DBI_execSql(str) " while processing one of these split statements, processing is stopped " and the error is displayed to the user. if split_on_pattern == "" + " Strip trailing cmd terminators, they should not be required + " and some drivers complain (Oracle) + let str = substitute(str, '\s*'.cmd_terminator.'$', '', '') return s:DB_DBI_execStr(str) else let statements = split("\n".str, split_on_pattern) @@ -4762,7 +4785,10 @@ function! s:DB_DBI_execSql(str) for sql in statements if sql !~ '^[ \t\n]*$' " Strip leading and trailing newlines - let sql = substitute(sql, '^\n*\(.\{-}\)\n*$', '\1', 'g') + let sql = substitute(sql, '^\n*\(.\{-}\)\n*$', '\1', 'g') + " Strip trailing cmd terminators, they should not be required + " and some drivers complain (Oracle) + let sql = substitute(sql, '\s*'.cmd_terminator.'$', '', '') let result = s:DB_DBI_execStr(sql) let results = add( results, result ) if result == -1 @@ -4784,7 +4810,7 @@ function! s:DB_DBI_execStr(str) return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect(a:str) == -1 return -1 endif @@ -4814,7 +4840,7 @@ function! s:DB_DBI_describeTable(table_name) return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("describe " . a:table_name) == -1 return -1 endif @@ -4842,7 +4868,7 @@ function! s:DB_DBI_describeProcedure(procedure_name) return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("describe " . a:procedure_name) == -1 return -1 endif @@ -4908,7 +4934,7 @@ function! s:DB_DBI_getListColumn(table_name) return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("column list " . a:table_name) == -1 return -1 endif @@ -4966,7 +4992,7 @@ function! s:DB_DBI_getListTable(table_prefix) return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("table list " . a:table_prefix) == -1 return -1 endif @@ -4994,6 +5020,42 @@ function! s:DB_DBI_getListTable(table_prefix) return result endfunction +function! s:DB_DBI_extractDdl(object_name_like) + "let owner = s:DB_getObjectOwner(a:object_name_like) + "let table_name = s:DB_getObjectName(a:object_name_like) + + if s:DB_DBI_Autoload() == -1 + return -1 + endif + + if dbext#DB_connect("extract ddl " . a:object_name_like) == -1 + return -1 + endif + + " If empty, use undef, if not, place single quotes around it and add a % + "let owner = (owner == ''?'undef':"'".owner."%'") + "let table_name = (table_name == ''?'undef':"'".table_name."%'") + "let driver = s:DB_get('driver') + "let table_type = s:DB_getDefault('DBI_table_type_'.driver) + "if table_type == "" + " let table_type = s:DB_getDefault('DBI_table_type') + "endif + "let table_type = "'".table_type."'" + + let cmd = "perl db_ora_extract_ddl('".a:object_name_like."')" + exec cmd + if g:dbext_dbi_result == -1 + " call s:DB_errorMsg(g:dbext_dbi_msg) + call s:DB_runCmd("perl DBI", cmd, g:dbext_dbi_msg) + return -1 + endif + + let result = g:dbext_dbi_result + call s:DB_runCmd("perl DBI", cmd, result) + + return result +endfunction + function! s:DB_DBI_getListProcedure(proc_prefix) let owner = s:DB_getObjectOwner(a:proc_prefix) let object = s:DB_getObjectName(a:proc_prefix) @@ -5002,7 +5064,7 @@ function! s:DB_DBI_getListProcedure(proc_prefix) return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("procedure list " . a:proc_prefix) == -1 return -1 endif @@ -5048,7 +5110,7 @@ function! s:DB_DBI_getListView(view_prefix) return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("view list " . a:view_prefix) == -1 return -1 endif @@ -5076,7 +5138,7 @@ function! s:DB_DBI_getDictionaryTable() "{{{ return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("table dictionary") == -1 return -1 endif @@ -5172,7 +5234,7 @@ function! s:DB_DBI_getDictionaryProcedure() "{{{ return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("procedure dictionary") == -1 return -1 endif @@ -5216,7 +5278,7 @@ function! s:DB_DBI_getDictionaryView() "{{{ return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("view dictionary") == -1 return -1 endif @@ -5310,7 +5372,7 @@ function! s:DB_DBI_setOption(option_name, value) "{{{ return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("DBI options") == -1 return -1 endif @@ -5331,7 +5393,7 @@ function! s:DB_ODBC_execSql(str) return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("ODBC options") == -1 return -1 endif @@ -5361,7 +5423,7 @@ function! s:DB_ODBC_describeTable(table_name) return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("describe table " . a:table_name) == -1 return -1 endif @@ -5389,7 +5451,7 @@ function! s:DB_ODBC_describeProcedure(procedure_name) return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("describe procedure " . a:procedure_name) == -1 return -1 endif @@ -5482,7 +5544,7 @@ function! s:DB_ODBC_getListColumn(table_name) return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("column list " . a:table_name) == -1 return -1 endif @@ -5540,7 +5602,7 @@ function! s:DB_ODBC_getListTable(table_prefix) return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("table list " . a:table_prefix) == -1 return -1 endif @@ -5570,7 +5632,7 @@ function! s:DB_ODBC_getListProcedure(proc_prefix) return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("procedure list " . a:proc_prefix) == -1 return -1 endif @@ -5643,7 +5705,7 @@ function! s:DB_ODBC_getListView(view_prefix) return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("view list " . a:view_prefix) == -1 return -1 endif @@ -5671,7 +5733,7 @@ function! s:DB_ODBC_getDictionaryTable() "{{{ return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("table dictionary") == -1 return -1 endif @@ -5757,7 +5819,7 @@ function! s:DB_ODBC_getDictionaryProcedure() "{{{ return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("procedure dictionary") == -1 return -1 endif @@ -5828,7 +5890,7 @@ function! s:DB_ODBC_getDictionaryView() "{{{ return -1 endif - if dbext#DB_connect() == -1 + if dbext#DB_connect("view dictionary") == -1 return -1 endif @@ -6156,9 +6218,37 @@ function! s:DB_getLoginScript(filename) endif endif + "ans: debug the login_script + "echomsg "login_script:" + "echomsg sql return sql endfunction +function! dbext#DB_enableSrvOut(...) + + let cmd = "perl db_enable_srv_out('dummy')" + exec cmd + if g:dbext_dbi_result == -1 + " call s:DB_errorMsg(g:dbext_dbi_msg) + call s:DB_runCmd("perl DBI", cmd, g:dbext_dbi_msg) + return -1 + endif + +endfunction + +function! dbext#DB_disableSrvOut(...) + + + let cmd = "perl db_disable_srv_out('dummy')" + exec cmd + if g:dbext_dbi_result == -1 + " call s:DB_errorMsg(g:dbext_dbi_msg) + call s:DB_runCmd("perl DBI", cmd, g:dbext_dbi_msg) + return -1 + endif + +endfunction + function! dbext#DB_describeTable(...) if(a:0 > 0) let table_name = s:DB_getObjectAndQuote(a:1) @@ -6278,6 +6368,25 @@ function! dbext#DB_getListTable(...) return dbext#DB_execFuncTypeWCheck('getListTable', table_prefix) endfunction +function! dbext#DB_extractDdl(...) + if(a:0 > 0) + " Strip any leading or trailing spaces + let object_name_like = substitute(a:1,'\s*\(\w*\)\s*','\1','') + else + let object_name_like = s:DB_getInput( + \ "Enter object_name_like: ", + \ '', + \ "dbext_cancel" + \ ) + if object_name_like == "dbext_cancel" + return "" + endif + endif + " it is only oracle and DBI specific, but we need generic handling fro + " winnr() etc to assign s:dbext_prev_winnr and friends properly... + return dbext#DB_execFuncTypeWCheck('extractDdl', object_name_like) +endfunction + function! dbext#DB_getListProcedure(...) if(a:0 > 0) " Strip any leading or trailing spaces @@ -6346,6 +6455,10 @@ endfunction "}}} " General {{{ +function! s:DB_infoMsg(msg) + echomsg a:msg +endfunction + function! s:DB_warningMsg(msg) echohl WarningMsg echomsg a:msg @@ -7115,6 +7228,343 @@ function! s:DB_runCmd(cmd, sql, result) return endfunction "}}} +" JobSupport {{{ +function! s:DB_runCmdJobSupport(binary, args, sql, result) + let cmd = a:binary . ' ' . a:args + if s:dbext_job_support == 0 || s:DB_get('job_enable') == 0 || s:DB_get('use_result_buffer') != 1 + " s:dbext_job_support == 0 - Vim not compiled with Job / Channel support + " g:dbext_default_job_enable == 0 - User doesn't want to use Jobs + " s:DB_get('use_result_buffer') != 1 - User requsted the results as a string then + " we cannot use the asynchronous support + return s:DB_runCmd(cmd, a:sql, a:result) + endif + + let s:dbext_prev_sql = a:sql + let s:dbext_job_result = "" + let s:dbext_job_elapsed = 0 + let s:dbext_job_timer_id = 0 + let s:dbext_job_cmd = cmd + let s:dbext_job_sql = a:sql + let l:job_bufnr = 0 + + let l:options = {} + let l:options['callback'] = function('s:DB_runCmdJobOnCallback') + let l:options['close_cb'] = function('s:DB_runCmdJobClose') + let l:options['exit_cb'] = function('s:DB_runCmdJobOnExit') + let l:options['out_io'] = 'pipe' + let l:options['err_io'] = 'out' + let l:options['in_io'] = 'null' + if cmd =~ s:DB_get('job_pipe_regex') + " If the cmd pipes in the SQL from the dbext_tempfile + " then remove this from the command line and specify + " it using the in_io and in_name job options + let regex = '^\(.*\)\s\+' . s:DB_get('job_pipe_regex') . '\s*\(' . escape(s:dbext_tempfile, '\\/.*$^~[]') . '\)' + let cmd = substitute(cmd, regex, '\1', '') + let regex = s:DB_get('job_quotee_regex') + if regex != '' + let cmd = substitute(cmd, '"', '', 'g') + endif + "echomsg "DB_runCmdJobSupport: regex:" . regex + "echomsg "DB_runCmdJobSupport: cmd:" . cmd + "let cmd = substitute(cmd, '^\(.*\)\s\+<\s\+\(\S\+\)', 'cat \2 | \1', '') + " echomsg cmd + let l:options['in_io'] = 'file' + let l:options['in_name'] = s:dbext_tempfile + endif + let l:options['out_mode'] = 'nl' + let l:options['err_mode'] = 'nl' + let l:options['stoponexit'] = 'term' + + " Store current connection parameters + call s:DB_saveConnParameters() + + let l:display_cmd_line = s:DB_get('display_cmd_line') + + let l:job_bufnr = s:DB_addToResultBuffer('', "clear") + if l:display_cmd_line == 1 + let cmd_line = "Last command:\n" . + \ cmd . "\n" . + \ "Last SQL:\n" . + \ a:sql + + call s:DB_addToResultBuffer(cmd_line, "add") + endif + + " Return to original window + exec s:dbext_prev_winnr."wincmd w" + + "echomsg "DB_runCmdJobSupport: " . cmd + let s:dbext_job = job_start(cmd, l:options) + " let s:dbext_job = job_start( + " \ cmd, + " \ { + " \ 'out_cb': function('s:DB_runCmdJobOutput'), + " \ 'err_cb': function('s:DB_runCmdJobError'), + " \ 'close_cb': function('s:DB_runCmdJobClose') + " \ } + " \ ) + " "\ [&shell, &shellcmdflag, cmd], + " "\ 'out_io': 'buffer', + " "\ 'out_buf': l:job_bufnr, + " "\ 'out_modifiable': 0, + " "\ 'out_cb': function('s:DB_runCmdJobOutput'), + " "\ 'err_cb': function('s:DB_runCmdJobError'), + + if job_status(s:dbext_job) == "run" + call dbext#DB_jobTimerStart() + let job_msg = 'job started:' . + \ strftime("%H:%M:%S", localtime()) . + \ " updates every " . + \ s:DB_get('job_status_update_ms') . + \ " ms" + call s:DB_addToResultBuffer(job_msg, "add") + " if s:DB_get('job_show_msgs') == 1 + " call s:DB_infoMsg('dbext ' . job_msg) + " endif + else + let job_msg = "dbext job failed to start running without job. Error:" . string(job_info(s:dbext_job)) + call s:DB_addToResultBuffer(job_msg, "add") + " call s:DB_warningMsg("dbext job failed to start running without job. Error:" . string(job_info(s:dbext_job))) + " Try again without using jobs + return s:DB_runCmd(cmd, a:sql, a:result) + endif + " call s:DB_warningMsg("dbext job info:" . string(job_info(s:dbext_job))) + + return +endfunction +function! s:DB_runCmdJobOutput(channel, msg) + " echomsg "DB_runCmdJobOutput: " . ch_status(a:channel) . " msg:" . a:msg + let s:dbext_job_result .= a:msg . "\n" + return +endfunction +function! s:DB_runCmdJobError(channel, msg) + " echomsg "DB_runCmdJobError: " . ch_status(a:channel) . " msg:" . a:msg + let s:dbext_job_result .= a:msg . "\n" + return +endfunction +function! s:DB_runCmdJobUpdateStatus(timer) + if job_status(s:dbext_job) == "run" + let s:dbext_job_elapsed += s:DB_get('job_status_update_ms') + call s:DB_infoMsg( "dbext job status:" . + \ job_status(s:dbext_job) . + \ " running time(ms):" . + \ s:dbext_job_elapsed . + \ ' DBJobStop to cancel' + \ ) + else + call dbext#DB_jobTimerStop() + endif +endfunction +" invoked on "callback" when job output +function! s:DB_runCmdJobOnCallback(channel, msg) + " echomsg "DB_runCmdJobOnCallback ch:" . ch_status(a:channel) . " msg: " . a:msg + if !exists("s:dbext_job") + return + endif + if type(a:msg) != 1 + return + endif + let s:dbext_job_result .= a:msg . "\n" +endfunc +" invoked on "exit_cb" when job exited +function! s:DB_runCmdJobOnExit(job, msg) + " echomsg "DB_runCmdJobOnExit job:" . a:job . " msg: " . a:msg + " Stop the job status timer which runs forever + if s:dbext_job_timer_id > 0 + call dbext#DB_jobTimerStop() + else + return + endif + " This timer runs once and stops (milliseconds) + call timer_start(100, function('s:DB_runCmdJobFinish')) +endfunc +function! s:DB_runCmdJobClose(channel) + " Stop the job status timer which runs forever + if s:dbext_job_timer_id > 0 + call dbext#DB_jobTimerStop() + " call timer_stop(s:dbext_job_timer_id) + " let s:dbext_job_timer_id = 0 + else + return + endif + " out_cb (DB_runCmdJobOutput) may still be active at this point + " echomsg "DB_runCmdJobClose: " . ch_status(a:channel) + let l:limit = 128 + let l:options = {'timeout':0} + " This code is only required if the job is started + " without using out_cb and err_cb. + while ch_status(a:channel) == 'buffered' + try + let s:dbext_job_result .= ch_read(a:channel, l:options) . "\n" + if l:text == '' + " important when child process is killed + let l:limit -= 1 + if l:limit < 0 + break + endif + else + call s:DB_runCmdJobOnCallback(a:channel, l:text) + endif + catch /E906/ + " Seems to happen after running for a while + echomsg 'Job E906:' . strftime("%H:%M:%S", localtime()) + echomsg 'ch_status:' . ch_status(a:channel) + " call ch_close(a:channel) + break + endtry + "echomsg "DB_runCmdJobClose: " . ch_read(a:channel) + endwhile + " This timer runs once and stops (milliseconds) + call timer_start(100, function('s:DB_runCmdJobFinish')) +endfunction +function! s:DB_runCmdJobFinish(channel) + if s:DB_get('use_result_buffer') == 1 + let result = s:dbext_job_result + " Look up the binary return code from the job info + let l:shell_error = job_info(s:dbext_job).exitval + + if l:shell_error !~ '^\d\+$' + let l:shell_error = 0 + endif + + call s:DB_addToResultBuffer(result, "add") + + let l:db_type = s:DB_get('type') + let dbi_result = 0 + if exists("g:dbext_dbi_result") + let dbi_result = g:dbext_dbi_result + endif + + " If there was an error, show the command just executed + " for debugging purposes + if (l:shell_error && l:db_type !~ '\\|\') || + \ (dbi_result == -1 && l:db_type =~ '\\|\') + let output = "To change connection parameters:\n" . + \ ":DBPromptForBufferParameters\n" . + \ "Or\n" . + \ ":DBSetOption user\|passwd\|dsnname\|srvname\|dbname\|host\|port\|...=\n" . + \ ":DBSetOption user=tiger:passwd=scott\n" . + \ "Last command(rc=".l:shell_error."):\n" . + \ s:dbext_job_cmd . "\n" . + \ "Last SQL:\n" . + \ s:dbext_job_sql . "\n" + call s:DB_addToResultBuffer(output, "add") + + let idx = index(s:dbext_buffers_connected, bufnr('%')) + if idx > -1 + " Remove persistent connection + if s:DB_get('DBI_disconnect_onerror') == '1' + call dbext#DB_disconnect(bufnr('%')) + endif + endif + let s:dbext_job_elapsed += s:DB_get('job_status_update_ms') + let job_msg = "job ran for less than " . string(s:dbext_job_elapsed) . " ms" + call s:DB_addToResultBuffer(job_msg, "add") + else + if exists('*DBextPostResult') + let res_buf_name = s:DB_resBufName() + if s:DB_switchToBuffer(res_buf_name, res_buf_name, 'result_bufnr') == 1 + " Switch back to the result buffer and execute + " the user defined function + call DBextPostResult(l:db_type, (s:DB_get('result_bufnr')+0)) + endif + endif + if s:DB_get('autoclose') == '1' && s:dbext_result_count <= s:DB_get('autoclose_min_lines') + " Determine rows affected + if l:db_type !~ '\\|\' + call s:DB_{l:db_type}_stripHeaderFooter(result) + endif + if s:dbext_result_count >= 2 + if getline(2) !~ '^SQLCode:' + call dbext#DB_windowClose(s:DB_resBufName()) + echon 'dbext: Rows affected:'.g:dbext_rows_affected.' Autoclose enabled, DBSetOption autoclose=0 to disable' + endif + else + call dbext#DB_windowClose(s:DB_resBufName()) + " echon 'dbext: Autoclose enabled, DBSetOption autoclose=0 to disable' + echon 'dbext: Rows affected:'.g:dbext_rows_affected.' Autoclose enabled, DBSetOption autoclose=0 to disable' + endif + endif + endif + + " Return to original window + exec s:dbext_prev_winnr."wincmd w" + endif + + return +endfunction +function! dbext#DB_jobStop(...) + if s:dbext_job_support == 1 + if exists('s:dbext_job') + if a:0 > 0 + let job_stop_rc = job_stop(s:dbext_job, a:1) + else + let job_stop_rc = job_stop(s:dbext_job) + endif + if job_stop_rc == 0 + call s:DB_warningMsg("dbext job stop not supported:" . ((a:0 > 0) ? (a:1) : '')) + endif + let job_status = job_status(s:dbext_job) + if s:DB_get('job_show_msgs') == 1 + call s:DB_infoMsg("dbext job stopped:" . job_status) + endif + if job_status != "run" + call dbext#DB_jobTimerStop() + endif + else + call s:DB_warningMsg("dbext no active job") + endif + else + call s:DB_warningMsg("dbext Vim was not compiled with job support") + endif +endfunction +function! dbext#DB_jobStatus() + if s:dbext_job_support == 1 + if exists('s:dbext_job') + call s:DB_infoMsg("dbext job status:" . job_status(s:dbext_job)) + else + call s:DB_warningMsg("dbext no active job") + endif + else + call s:DB_warningMsg("dbext Vim was not compiled with job support") + endif +endfunction +function! dbext#DB_jobTimerStart() + if s:dbext_job_support == 1 + let job_status = '' + if exists('s:dbext_job') + let job_status = job_status(s:dbext_job) + endif + if job_status != "run" + call s:DB_warningMsg("dbext no active jobs, aborting timer start" . (job_status != '' ? ':'.job_status : '')) + return + endif + if !exists('s:dbext_job_timer_id') || s:dbext_job_timer_id == 0 + " If the job started, report it's status every 2 seconds + let s:dbext_job_timer_id = timer_start(s:DB_get('job_status_update_ms'), function('s:DB_runCmdJobUpdateStatus'), { 'repeat': -1 }) + else + call s:DB_warningMsg("dbext timer is already active, aborting timer start") + endif + else + call s:DB_warningMsg("dbext Vim was not compiled with job support") + endif +endfunction +function! dbext#DB_jobTimerStop() + if s:dbext_job_support == 1 + if exists('s:dbext_job_timer_id') + call timer_stop(s:dbext_job_timer_id) + let s:dbext_job_timer_id = 0 + if s:DB_get('job_show_msgs') == 1 + call s:DB_infoMsg("dbext job timer cancelled after " . string(s:dbext_job_elapsed) . " ms") + endif + else + call s:DB_warningMsg("dbext no active timer") + endif + else + call s:DB_warningMsg("dbext Vim was not compiled with job support") + endif +endfunction +"}}} " switchToBuffer {{{ function! s:DB_switchToBuffer(buf_name, buf_file, get_buf_nr_name) " Retieve this value before we switch buffers @@ -7419,6 +7869,7 @@ function! s:DB_addToResultBuffer(output, do_clear) " Store current window number so we can return to it " let cur_winnr = winnr() let res_buf_name = s:DB_resBufName() + let res_bufnr = 0 let conn_props = s:DB_getTitle() let dbi_orient = s:DB_get('DBI_orientation') @@ -7426,6 +7877,7 @@ function! s:DB_addToResultBuffer(output, do_clear) " Open buffer in required location if s:DB_switchToBuffer(res_buf_name, res_buf_name, 'result_bufnr') == 1 + let res_bufnr = bufnr('%') nnoremap R :DBResultsRefresh nnoremap O :DBOrientationToggle nnoremap dd :call dbext#DB_removeVariable() @@ -7476,6 +7928,7 @@ function! s:DB_addToResultBuffer(output, do_clear) " silent! exec "put = data" let cmd = "perl db_print_results('".dbi_orient."')" exec cmd + "norm Goans was here(2). else let g:dbext_rows_affected = 0 let l:start_of_output = line('$') @@ -7490,11 +7943,13 @@ function! s:DB_addToResultBuffer(output, do_clear) endif " Since this is a small window, remove any blanks lines - silent %g/^\s*$/d + " ans: this squashes my DDL output - because empty lines will be removed + "silent %g/^\s*$/d " Fix the ^M characters, if any silent execute "%s/\\\+$//e" " Dont allow modifications, and do not wrap the text, since " the data may be lined up for columns + "norm Goans was here. setlocal nomodified setlocal nowrap setlocal noswapfile @@ -7512,7 +7967,7 @@ function! s:DB_addToResultBuffer(output, do_clear) call s:DB_warningMsg("dbext: No previous window") endif - return + return res_bufnr endfunction "}}} " Parsers {{{ function! dbext#DB_parseQuery(query) @@ -7597,13 +8052,25 @@ function! s:DB_searchReplace(str, exp_find_str, exp_get_value, count_matches) let prompt_for_values = -1 let use_saved_vars = 0 let str = a:str + let index = 0 let count_nbr = 0 + let count_total = 0 let already_prompted = {} + if a:count_matches > 0 + " Determine how many matches in total prior to prompting + let index = match(str, a:exp_find_str, index) + while index > -1 + let count_total = count_total + 1 + let index = index + 1 + " Find next match + let index = match(str, a:exp_find_str, index) + endwhile + endif " Find the string index position of the first match let index = match(str, a:exp_find_str) while index > -1 " DEBUGGING - " This is a useful echo statemen to use inside the debug loop + " This is a useful echo statement to use inside the debug loop " when using breakadd " echo index matchstr(str, a:exp_find_str, index) var a:exp_find_str "\n" strpart(str, 0, (index-1)) @@ -7705,7 +8172,29 @@ function! s:DB_searchReplace(str, exp_find_str, exp_get_value, count_matches) if has_key(b:dbext_sqlvar_mv, var) let saved_values = b:dbext_sqlvar_mv[var] if !empty(saved_values) - let var_val = saved_values[0] + if var == '?' + " For question marks and 'Use previously saved values' + " if available, simply pull the value from the + " saved_values array. + " The values are stored in reverse order (MRU), so + " we must calculate which array entry to pull. + " if len(saved_values) >= count_nbr && (count_total - count_nbr) > len(saved_values) + if len(saved_values) >= count_nbr + " Reverse the array to be in the order + " the first values were used. + " Make sure we use the same # of values as + " the # of ?s so we use the correct ones. + let rev_saved_values = reverse(copy(saved_values)) + while len(rev_saved_values) > count_total + call remove(rev_saved_values, 0) + endwhile + let var_val = rev_saved_values[(count_nbr - 1)] + else + let var_val = saved_values[0] + endif + else + let var_val = saved_values[0] + endif else let var_val = '' endif @@ -7716,12 +8205,18 @@ function! s:DB_searchReplace(str, exp_find_str, exp_get_value, count_matches) if prompt_for_values == 3 && var_val != '' " Use previously saved value let response = 5 - elseif has_key(already_prompted, var) + elseif (has_key(already_prompted, var) && var != '?') || (has_key(already_prompted, var) && var == '?' && prompt_for_values != 1) + " Already prompted for ths value, re-use + " the # selected previously let response = already_prompted[var] else " If empty, check if they want to leave it empty " of skip this variable - let msg = "Choice for [".var."]:". + let display_var = var + if var == '?' + let display_var = display_var . ':' . count_nbr + endif + let msg = "Choice for [".display_var."]:". \ "\n1. Use [".var."]". \ "\n2. Use blank value". \ "\n3. Enter new value". @@ -7744,7 +8239,7 @@ function! s:DB_searchReplace(str, exp_find_str, exp_get_value, count_matches) let response = confirm(msg, choices, "1", "Question") " To prevent re-asking for a value for this query - " indicate we have already prompted + " indicate we have already prompted. let already_prompted[var] = response endif @@ -7755,19 +8250,21 @@ function! s:DB_searchReplace(str, exp_find_str, exp_get_value, count_matches) " Use variable as is " Add the string to the list remembered variable assignments let index = index + strlen(var) + 1 - if a:count_matches != 1 + "if a:count_matches != 1 " Add this assignment to the list of remembered " assignments unless it is a question mark - " used as a host variable + " used as a host variable. + " Version 24 allows remembering question marks call dbext#DB_sqlVarAssignment(0, 'set '.var.' = '.var) - endif + "endif elseif response == 2 " Use blank let var_val = '' let replace_sub = '\%'.(index+1).'c'.'.\{'.strlen(var).'}' let str = substitute(str, replace_sub, var_val, '') let index = index + strlen(var_val) + 1 - if a:count_matches != 1 && s:DB_get('variable_remember') == '1' + "if a:count_matches != 1 && s:DB_get('variable_remember') == '1' + if s:DB_get('variable_remember') == '1' " Add this assignment to the list of remembered " assignments unless it is a question mark " used as a host variable @@ -7796,14 +8293,17 @@ function! s:DB_searchReplace(str, exp_find_str, exp_get_value, count_matches) let replace_sub = '\%'.(index+1).'c'.'.\{'.strlen(var).'}' let str = substitute(str, replace_sub, var_val, '') let index = index + strlen(var_val) + 1 - if a:count_matches != 1 && s:DB_get('variable_remember') == '1' + "if a:count_matches != 1 && s:DB_get('variable_remember') == '1' + if s:DB_get('variable_remember') == '1' " Add this assignment to the list of remembered " assignments unless it is a question mark " used as a host variable call dbext#DB_sqlVarAssignment(0, 'set '.var.' = '.var_val) " Override their choice so if the variable " is used another time, use the saved value - let already_prompted[var] = 4 + if var != '?' + let already_prompted[var] = 5 + endif endif elseif response == 4 " Display the saved variables screen and abort @@ -7832,9 +8332,10 @@ function! s:DB_searchReplace(str, exp_find_str, exp_get_value, count_matches) let var_val = saved_values[value_idx] let replace_sub = '\%'.(index+1).'c'.'.\{'.strlen(var).'}' let str = substitute(str, replace_sub, var_val, '') - if a:count_matches != 1 && s:DB_get('variable_remember') == '1' + "if a:count_matches != 1 && s:DB_get('variable_remember') == '1' + if s:DB_get('variable_remember') == '1' " Move this to the top of the MRU list of - " previously saved values unless it is a question mark + " previously saved values call dbext#DB_sqlVarAssignment(0, 'set '.var.' = '.var_val) endif endif @@ -7864,7 +8365,7 @@ function! s:DB_searchReplace(str, exp_find_str, exp_get_value, count_matches) return str endfunction -function! s:DB_searchReplaceSavedVariable(str, exp_find_str, exp_get_value, count_matches) +function! s:DB_searchReplace23(str, exp_find_str, exp_get_value, count_matches) " If query is empty, ignore if a:str == "" return "" @@ -7961,31 +8462,321 @@ function! s:DB_searchReplaceSavedVariable(str, exp_find_str, exp_get_value, coun " Quickly check with the user to determine " if they even want to prompt, or to simply " execute the query as is - let response = confirm("1. Prompt for variables" . - \ "\n2. Execute query as is" . - \ "\n3. Never prompt" - \ , "&1\n&2\n&3" - \ ) - if response == 1 + let prompt_for_values = confirm("Variables detected in statement. Choose an action:". + \ "\n1. Prompt for variables". + \ "\n2. Execute query as is". + \ "\n3. Use previously saved values". + \ "\n4. Never ask again". + \ "\n5. Abort" + \ , "&1\n&2\n&3\n&4\n&5" + \ , "1" + \ , "Question" + \ ) + if prompt_for_values == 0 + "Abort + return "" + elseif prompt_for_values == 1 let prompt_for_values = 1 - elseif response == 2 + elseif prompt_for_values == 2 return str - elseif response == 3 + elseif prompt_for_values == 3 + " Use previously saved values when you can + elseif prompt_for_values == 4 " Never Prompt for variables for this buffer call s:DB_set("always_prompt_for_variables", '-1') return str + elseif prompt_for_values == 5 + " Abort + return "" endif - let response = 2 endif + if has_key(b:dbext_sqlvar_mv, var) + let saved_values = b:dbext_sqlvar_mv[var] + if !empty(saved_values) + let var_val = saved_values[0] + else + let var_val = '' + endif + else + let var_val = '' + endif + + if prompt_for_values == 3 && var_val != '' + " Use previously saved value + let response = 5 + elseif has_key(already_prompted, var) + let response = already_prompted[var] + else + " If empty, check if they want to leave it empty + " of skip this variable + let msg = "Choice for [".var."]:". + \ "\n1. Use [".var."]". + \ "\n2. Use blank value". + \ "\n3. Enter new value". + \ "\n4. Show previously saved values" + let choices = "&1\n&2\n&3\n&4" + + " Dynamically build the list of choices for the + " user based on previously saved values + if exists("saved_values") && type(saved_values) == 3 + let msg = msg. "\n5. Use most recent saved value" + let choices = choices."\n&5" + let choice_nbr = 6 + for item in saved_values + let msg = msg. + \ "\n".string(choice_nbr).". Use [".item."]" + let choices = choices."\n&".string(choice_nbr) + let choice_nbr = choice_nbr + 1 + endfor + endif + let response = confirm(msg, choices, "1", "Question") + + " To prevent re-asking for a value for this query + " indicate we have already prompted + let already_prompted[var] = response + endif + + if response == 0 + " Abort + return "" + elseif response == 1 + " Use variable as is + " Add the string to the list remembered variable assignments + let index = index + strlen(var) + 1 + if a:count_matches != 1 + " Add this assignment to the list of remembered + " assignments unless it is a question mark + " used as a host variable + call dbext#DB_sqlVarAssignment(0, 'set '.var.' = '.var) + endif + elseif response == 2 + " Use blank + let var_val = '' + let replace_sub = '\%'.(index+1).'c'.'.\{'.strlen(var).'}' + let str = substitute(str, replace_sub, var_val, '') + let index = index + strlen(var_val) + 1 + if a:count_matches != 1 && s:DB_get('variable_remember') == '1' + " Add this assignment to the list of remembered + " assignments unless it is a question mark + " used as a host variable + call dbext#DB_sqlVarAssignment(0, 'set '.var.' = '.var_val) + endif + elseif response == 3 + " Prompt the user using the name of the variable + let dialog_msg = "Enter value for " . var + if a:count_matches == 1 + " If there is no name (ie ?), then include the + " count of what was found so the user can + " distinguish between different ?s + let dialog_msg = dialog_msg . " number " . count_nbr + endif + let dialog_msg = dialog_msg . ": " + let var_val = s:DB_getInput( + \ dialog_msg, + \ "", + \ "dbext_cancel" + \ ) + let response = 2 + " Ok or Cancel result in an empty string + if var_val == "dbext_cancel" + return str + endif + let replace_sub = '\%'.(index+1).'c'.'.\{'.strlen(var).'}' + let str = substitute(str, replace_sub, var_val, '') + let index = index + strlen(var_val) + 1 + if a:count_matches != 1 && s:DB_get('variable_remember') == '1' + " Add this assignment to the list of remembered + " assignments unless it is a question mark + " used as a host variable + call dbext#DB_sqlVarAssignment(0, 'set '.var.' = '.var_val) + " Override their choice so if the variable + " is used another time, use the saved value + " let already_prompted[var] = 4 + endif + elseif response == 4 + " Display the saved variables screen and abort + " query execution + exec 'DBListVar' + return "" + elseif response == 5 + " Use previously saved value + let replace_sub = '\%'.(index+1).'c'.'.\{'.strlen(var).'}' + let str = substitute(str, replace_sub, var_val, '') + let index = index + strlen(var_val) + 1 + elseif response == 6 + " Use previously saved value + let replace_sub = '\%'.(index+1).'c'.'.\{'.strlen(var).'}' + let str = substitute(str, replace_sub, var_val, '') + let index = index + strlen(var_val) + 1 + else + " 5, 6 are really the same value based on MRU from the + " list of previously saved values. + " Anything above 6, indicates which position in the + " list. + " 6 = [0], 7 = [1], 8 = [2] + let value_idx = (response + 0) - 6 + if exists("saved_values") && type(saved_values) == 3 + if !empty(saved_values) && len(saved_values) >= value_idx + let var_val = saved_values[value_idx] + let replace_sub = '\%'.(index+1).'c'.'.\{'.strlen(var).'}' + let str = substitute(str, replace_sub, var_val, '') + if a:count_matches != 1 && s:DB_get('variable_remember') == '1' + " Move this to the top of the MRU list of + " previously saved values unless it is a question mark + call dbext#DB_sqlVarAssignment(0, 'set '.var.' = '.var_val) + endif + endif + endif + let index = index + strlen(var_val) + 1 + endif + endif + else + " Move on to next match as this variable was part of a + " comment line + let index = index + strlen(var) + 1 + endif + else + if s:DB_get('variable_remember') == '1' + " Remember this as only a temporary variable and remove + " these when a new query begins + call dbext#DB_sqlVarAssignment(2, 'set '.var.' = '.var) + endif + " Skip this match and move on to the next + " let index = match(str, a:exp_find_str, index+strlen(var)) + 1 + let index = index + strlen(var) + 1 + endif + + " Find next match + let index = match(str, a:exp_find_str, index) + endwhile + + return str +endfunction +function! s:DB_searchReplaceSavedVariable(str, exp_find_str, exp_get_value, count_matches) + " If query is empty, ignore + if a:str == "" + return "" + endif + + " Check if the user has chosen to "Stop Prompting" for this query + if s:DB_get("stop_prompt_for_variables") == 1 + return a:str + endif + + if s:DB_get("always_prompt_for_variables") == "-1" + " Never try to parse the query + return a:str + endif + + " An additional check to see if the user wants to prompt + " for this query + let prompt_for_values = -1 + let use_saved_vars = 0 + let str = a:str + let count_nbr = 0 + let already_prompted = {} + " Find the string index position of the first match + let index = match(str, a:exp_find_str) + while index > -1 + " DEBUGGING + " This is a useful echo statemen to use inside the debug loop + " when using breakadd + " echo index matchstr(str, a:exp_find_str, index) var a:exp_find_str "\n" strpart(str, 0, (index-1)) + + let count_nbr = count_nbr + 1 + " Retrieve the name of what we found + " let var = matchstr(str, a:exp_get_value, index) + let var = matchstr(str, a:exp_find_str, index) + + " Check if this is part of a parameter definition + " IN @variable CHAR(1) + " OUT @variable CHAR(1) + " INOUT @variable CHAR(1) + " DECLARE @variable CHAR(1) + " VARIABLE @variable CHAR(1) -- CREATE VARIABLE @variable + " Or part of a string + " '@variable' + " Or part of path + " /@variable' + " Or a global variable + " SET @@variable = ... + " Or the definition of a global variable + " CREATE VARIABLE variable ... + " If so, ignore the match + " Regex + " \( - Start multiple matches + " \< - Start word boundary + " \w\+ - Match any word character + " \ze - Stop the match + " \s* - Match can be followed by spaces or tabs + " $ - And ends at the end of the line + " \| - OR + " '' - empty string + " \ze - Stop the match + " $ - And ends at the end of the line + " \| - OR + " / - Literal + " \ze - Stop the match + " $ - And ends at the end of the line + " \| - OR + " ? - Literal + " \ze - Stop the match + " $ - And ends at the end of the line + " \| - OR + " @ - Literal + " \ze - Stop the match + " $ - And ends at the end of the line + " \) - End multiiple matches + " + let inout = matchstr(strpart(str, 0, index), '\(\<\w\+\ze\s*$\|''\ze$\|/\ze$\|@\ze$\)') + + " The above query gathers the preceeding text to make the above + " determination + " if inout !~? '\(in\|out\|inout\|declare\|set\|variable\|''\|/\|@\)' + if inout == '' || s:DB_get('ignore_variable_regex') !~? inout + " Check if the variable name is preceeded by a comment character. + " If so, ignore and continue. + if strpart(str, 0, (index-1)) !~ '\(--\|\/\/\)\s*$' + " Check to see if the variable is part of the temporarily + " stored list of variables to ignore + if has_key(b:dbext_sqlvar_temp_mv, var) + " Ingore match and move on + " let index = match(str, a:exp_find_str, index+strlen(var)) + let index = index + strlen(var) + 1 + else + if prompt_for_values == -1 + " First time we are prompting for values + " Quickly check with the user to determine + " if they even want to prompt, or to simply + " execute the query as is + let response = confirm("1. Prompt for variables" . + \ "\n2. Execute query as is" . + \ "\n3. Never prompt" + \ , "&1\n&2\n&3" + \ ) + if response == 1 + let prompt_for_values = 1 + elseif response == 2 + return str + elseif response == 3 + " Never Prompt for variables for this buffer + call s:DB_set("always_prompt_for_variables", '-1') + return str + endif + let response = 2 + endif + + + " Prompt for value and continue + let response = 2 - " Prompt for value and continue - let response = 2 - if !has_key(already_prompted, var) && has_key(b:dbext_sqlvar_mv, var) && use_saved_vars != 1 " To prevent re-asking for a value for this query " indicate we have already prompted - let already_prompted[var] = 1 + if var != '?' + let already_prompted[var] = 1 + endif let var_val = b:dbext_sqlvar_mv[var] let dialog_msg = "Use a previously saved value?\n". @@ -8416,6 +9207,12 @@ endfunction function! s:DB_parseJava(query) let query = a:query + " Remove any line continuation characters + " SELECT c1 \ + " , c2 \ + " FROM t1 + let query = substitute(query, '\zs\\\s*\ze'."\n", ' ', 'g') + " Remove any newline characters let query = substitute(query, "\n", ' ', 'g') @@ -9053,8 +9850,7 @@ function! s:DB_historyAdd(sql) " Return to original window " exec cur_winnr."wincmd w" - exec s:dbext_prev_winnr."wincmd w" - + " exec s:dbext_prev_winnr."wincmd w" endfunction function! s:DB_historyUse(line) @@ -9210,6 +10006,23 @@ function! dbext#DB_commit(...) let bufnr = bufnr("%") endif + let type = s:DB_get('type') + if (type !~ '\\|\') + call s:DB_warningMsg( + \ "dbext:Connect and Disconnect functionality only available ". + \ "when using the DBI or ODBC interfaces" + \ ) + return -1 + endif + + if (type =~ '\') + let driver = 'ODBC' + let conn_parms = s:DB_get("dsnname") + else + let driver = s:DB_get('driver') + let conn_parms = s:DB_get("conn_parms") + endif + let bufnr = bufnr + 0 let idx = index(s:dbext_buffers_connected, bufnr) if idx > -1 @@ -9225,7 +10038,7 @@ function! dbext#DB_commit(...) return -1 endif - perl db_commit(bufnr) + exec "perl db_commit(".bufnr.")" if g:dbext_dbi_result == -1 call s:DB_runCmd("perl ".driver, "COMMIT", g:dbext_dbi_msg) return -1 @@ -9251,6 +10064,23 @@ function! dbext#DB_rollback(...) let bufnr = bufnr("%") endif + let type = s:DB_get('type') + if (type !~ '\\|\') + call s:DB_warningMsg( + \ "dbext:Connect and Disconnect functionality only available ". + \ "when using the DBI or ODBC interfaces" + \ ) + return -1 + endif + + if (type =~ '\') + let driver = 'ODBC' + let conn_parms = s:DB_get("dsnname") + else + let driver = s:DB_get('driver') + let conn_parms = s:DB_get("conn_parms") + endif + let bufnr = bufnr + 0 let idx = index(s:dbext_buffers_connected, bufnr) if idx > -1 @@ -9261,7 +10091,7 @@ function! dbext#DB_rollback(...) return -1 endif - perl db_rollback(bufnr) + exec "perl db_rollback(".bufnr.")" if g:dbext_dbi_result == -1 call s:DB_runCmd("perl ".driver, "ROLLBACK", g:dbext_dbi_msg) return -1 @@ -9271,7 +10101,7 @@ function! dbext#DB_rollback(...) return 0 endfunction -function! dbext#DB_connect() +function! dbext#DB_connect(str) " Only valid for DBI and ODBC (perl) let type = s:DB_get('type') if (type !~ '\\|\') @@ -9301,6 +10131,11 @@ function! dbext#DB_connect() call s:DB_runCmd("perl ".driver, cmd, g:dbext_dbi_msg) return -1 endif + if g:dbext_dbi_result == "1" + " Already connected + call s:DB_addConnected( bufnr('%') ) + return 0 + endif " Each time we issue a connect, set the max rows, this " will ensure it is updated each time the user @@ -9325,7 +10160,9 @@ function! dbext#DB_connect() let cmd = "perl db_connect('".driver."', '".conn_parms."', '".user."', '".passwd."')" exec cmd if g:dbext_dbi_result == -1 - call s:DB_runCmd("perl ".driver, cmd, g:dbext_dbi_msg) + "call s:DB_runCmd("perl ".driver, cmd, g:dbext_dbi_msg) + "call s:DB_runCmd(a:str, cmd, g:dbext_dbi_msg) + call s:DB_runCmd(cmd, a:str, g:dbext_dbi_msg) return -1 else call s:DB_addConnected( bufnr('%') ) @@ -9389,6 +10226,20 @@ function! dbext#DB_disconnect(...) endif let bufnr = bufnr + 0 + + " Check to see if we have a valid database + " connection, if not, nothing to do except + " cleanup + exec "perl db_is_connected( '".bufnr."' )" + if g:dbext_dbi_result == -1 + call s:DB_delConnected( bufnr ) + return -1 + endif + if g:dbext_dbi_result == "0" + " We are not connected + call s:DB_delConnected( bufnr ) + endif + let idx = index(s:dbext_buffers_connected, bufnr) if idx > -1 let type = s:DB_get('type') diff --git a/autoload/dbext_dbi.vim b/autoload/dbext_dbi.vim index 60fba5c..914fc0a 100644 --- a/autoload/dbext_dbi.vim +++ b/autoload/dbext_dbi.vim @@ -1,4 +1,4 @@ -" dbext.vim - Commn Database Utility +" dbext.vim - Common Database Utility " Copyright (C) 2002-16, Peter Bagyinszki, David Fishburn " --------------------------------------------------------------- " File: dbext_dbi.vim @@ -7,10 +7,10 @@ " It adds transaction support and the ability " to reach any database currently supported " by Perl and DBI. -" Version: 23.00 +" Version: 26.00 " Maintainer: David Fishburn " Authors: David Fishburn -" Last Modified: 2015 Jan 06 +" Last Modified: 2016 Sep 04 " Created: 2007-05-24 " Homepage: http://vim.sourceforge.net/script.php?script_id=356 " @@ -70,7 +70,7 @@ " " Installing the Oracle DBI module " cd Perl_Root_dir\bin -" ppm-shell.bat +" ppm.bat " install DBD::Oracle " quit " @@ -100,21 +100,37 @@ " " Installing the binary MySQL DBI module " cd Perl_Root_dir\bin -" ppm-shell.bat +" ppm.bat " install DBD-mysql " quit " +" Installing the binary SQLite DBI module +" cd Perl_Root_dir\bin +" perl -MCPAN -e shell +" install DBD::SQLite +" quit +" " Installing the Sybase ASE or SQL Server DBI module " http://lists.ibiblio.org/pipermail/freetds/2001q3/004748.html " cd Perl_Root_dir\bin -" ppm-shell.bat +" ppm.bat " install Sybase-TdsServer " Testing: " http://www.easysoft.com/developer/languages/perl/sql_server_unix_tutorial.html " perl -MCPAN -e shell " perl -e "use DBD::ODBC;" " perl -MDBD::ODBC -e "print $DBD::ODBC::VERSION;" +" This will show the various DBI drivers installed on your system: " perl -MDBI -e "DBI->installed_versions;" +" Perl : 5.020002 (MSWin32-x86-multi-thread-64int) +" OS : MSWin32 (6.3) +" DBI : 1.636 +" DBD::mysql : 4.036 +" DBD::SQLite : 1.50 +" DBD::SQLAnywhere: 2.13 +" DBD::Pg : 3.5.1 +" DBD::ODBC : 1.50 +" DBD::Crate : 0.0.1 " " Usage: " dbext_dbi.vim is designed to be used by the dbext.vim plugin. @@ -140,14 +156,14 @@ if exists("g:loaded_dbext_dbi") finish endif -let g:loaded_dbext_dbi = 2300 +let g:loaded_dbext_dbi = 2600 " Turn on support for line continuations when creating the script let s:cpo_save = &cpo set cpo&vim function! dbext_dbi#DBI_initialize() - if !exists("dbext_dbi_debug") + if !exists("g:dbext_dbi_debug") let g:dbext_dbi_debug = 0 endif if !exists("dbext_dbi_result") @@ -202,7 +218,7 @@ endif " sub db_remove_newlines " sub db_get_available_drivers " - Returns a list of installed DBI drivers -" sub db_list_connections +" db_list_connections " - Lists all open database connections " sub db_get_info " - Returns information about the DBI driver @@ -290,12 +306,17 @@ use diagnostics; use warnings; use strict; use Data::Dumper qw( Dumper ); +use utf8; +use open ':std', ':encoding(UTF-8)'; use DBI; +use DBD::Oracle qw(:ora_types); +use Encode; my %connections; my @result_headers; my @result_set; my @result_col_length; +my @dbms_output; my $result_max_col_width = 0; my $max_rows = 300; my $min_col_width = 4; # First NULL @@ -514,6 +535,7 @@ sub db_vim_print } if ( ! defined($line_nbr) ) { db_echo('db_vim_print invalid line number'); + db_debug("db_vim_print: line_nbr is invalid"); return -1; } @@ -521,7 +543,13 @@ sub db_vim_print $line_txt = ""; } - my @lines = split("\n", $line_txt); + my @lines = (); + if (!defined ($line_txt) || length($line_txt) == 0) { + # from dbms_output it is possible to get empty lines to print + @lines = (""); + } else { + @lines = split("\n", $line_txt); + } foreach my $line (@lines) { if ( $printed_lines > 0 ) { @@ -534,6 +562,7 @@ sub db_vim_print } # $main::curbuf->Append($line_nbr, $line); db_vim_op("Append", $line_nbr, $line); + db_debug("db_vim_print: line_nbr=$line_nbr, line=$line"); $line_nbr++; $printed_lines++; } @@ -779,6 +808,7 @@ sub db_get_connection my $bufnr = shift; my $driver = ''; my $conn_local; + my $z_user_data; if( ! defined($bufnr) ) { db_debug('db_get_connected:$bufnr undefined'); @@ -807,11 +837,12 @@ sub db_get_connection db_debug('db_get_connection:returning:'.$bufnr); db_debug("db_get_connection:".Dumper($connections{$bufnr})); - $conn_local = $connections{$bufnr}->{'conn'}; - $driver = $connections{$bufnr}->{'driver'}; + $conn_local = $connections{$bufnr}->{'conn'}; + $driver = $connections{$bufnr}->{'driver'}; + $z_user_data = $connections{$bufnr}->{z_user_data}; $connections{$bufnr}->{LastRequest} = localtime; - return ($conn_local, $driver); + return ($conn_local, $driver, $z_user_data); } db_set_vim_var('g:loaded_dbext_dbi_msg', 'db_check_error'); @@ -942,6 +973,8 @@ sub db_connect LongTruncOk => 1, RaiseError => 0, PrintError => 0, + ora_charset => 'AL32UTF8', + ora_ncharset => 'AL32UTF8', PrintWarn => 0 } ); # or die $DBI::errstr; @@ -976,6 +1009,7 @@ sub db_connect ,'AutoCommit' => 1 ,'CommitOnDisconnect' => 1 ,'LastRequest' => localtime + ,z_user_data => {} }; # db_debug('db_connected:checking if successful'); # if ( ! db_is_connected() ) { @@ -991,6 +1025,11 @@ sub db_connect } db_debug("db_connect:Connection Successful"); + + # ans, 2022-04-09 enable server output by default + &db_enable_srv_out; + db_debug("db_enable_srv_out done."); + return 0; } @@ -1024,7 +1063,7 @@ sub db_disconnect db_debug("db_disconnect:B:$bufnr A:".$conn_local->{AutoCommit}." C:".$connections{$bufnr}->{'CommitOnDisconnect'}); if( $conn_local->{AutoCommit} == 0 && $connections{$bufnr}->{'CommitOnDisconnect'} == 1 ) { - db_debug('db_disconnected: forcing COMMIT'); + db_debug('db_disconnected:forcing COMMIT'); $conn_local->commit; } @@ -1213,7 +1252,7 @@ sub db_query if ( ! $msg eq "" ) { $msg = "$level. DBQp:".(($level ne "I")?"SQLCode:$err:":"").$msg.(($state ne "")?":$state":""); db_set_vim_var('g:dbext_dbi_msg', $msg); - if ( $level eq "E" ) { + if ( $level eq "E" || ! defined($sth) ) { db_set_vim_var('g:dbext_dbi_result', -1); db_debug("db_query:$msg - exiting"); return -1; @@ -1248,6 +1287,88 @@ sub db_query return 0; } +# Fetches all available rows of dbms_output from server +sub fetch_dbms_output +{ + my ($conn_local) = (@_); + + my $max_line_size = 32768; # anything we put here can be too short... + #my $max_line_size = 500; # anything we put here can be too short... + my $get_lines_st = $conn_local->prepare_cached("begin dbms_output.get_lines(:l,:n); end;"); + my $num_lines_asked = 500; + my $num_lines = $num_lines_asked; + my @lines = map { ''; } ( 1..$num_lines ); # create 500 elements array + db_debug("\@lines size = ", scalar(@lines)); + my $lines_cur = \@lines; + $get_lines_st->bind_param_inout(':l', \$lines_cur, $max_line_size, {ora_type => ORA_VARCHAR2_TABLE}); + $get_lines_st->bind_param_inout(':n', \$num_lines, 50, {ora_type => 1}); + + $get_lines_st->execute(); + db_debug("executed get_lines() ok. num_lines fetched = $num_lines"); + + my @text_2 = (); + if ($num_lines) { + push @text_2, @lines[0..($num_lines-1)]; # copy to @text array + } + + while ($num_lines == $num_lines_asked) { + $num_lines = $num_lines_asked; + @lines = map { ''; } ( 1..$num_lines ); # create 500 elements array + $get_lines_st->execute(); + db_debug("executed get_lines()/again ok. num_lines fetched = $num_lines"); + if ($num_lines) { + push @text_2, @lines[0..($num_lines-1)]; # copy to @text array + } + } + + $get_lines_st = undef; + + ## max_line_size exceeded { + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # it sounds like dirty hack. and it is in fact. + # seems that if the max_line_size is exceeded in bind_param_inout for + # ORA_VARCHAR2_TABLE, requested number (500) of completely empty (null) + # strings are returned. That makes no real sense to me, but at least we + # are trying to handle this condition "gracefully" - well, that has to + # be told to called normally and caller has to decide, what means + # "gracefully" in that case - we just replace all the NULL strings with + # diagnostic string which is visible to caller and can be interpreted + # by human eye at least. + # more straight approach is to die() and let caller intercept us with + # eval {} and how to proceed in case he recognize we had a problem here + my $undefsFound = 0; + LINE: for my $l (@text_2) { + if (! defined($l)) { + $undefsFound++; + #last LINE; + } + } + + # only in case $num_lines_asked and "number of lines returned" - e.g. entries + # count in @text_2 we have a problem. otherwise we assume it is really occasional + # strings of size = 0 returned by server + if ($undefsFound && $num_lines_asked == scalar(@text_2)) { + die ("E: some are found in result, requested: $num_lines_asked, returned: ".scalar(@text_2).", undef: $undefsFound"); + } + + ## if ($undefsFound) { + ## db_debug("undefs found in the dbms_output, probably max_line_size=$max_line_size were exceeded?"); + ## my @text_3 = @text_2; + ## @text_2 = (); + ## for my $l (@text_3) { + ## if (! defined($l)) { + ## push @text_2, "max_line_size=$max_line_size exceeded?"; + ## } else { + ## push @text_2, $l; + ## } + ## } + ## } + ## max_line_size exceeded } + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @text_2; +} + db_set_vim_var('g:loaded_dbext_dbi_msg', 'db_format_results'); # Loops through the results and creates an array for display in a Vim buffer. # Only gathers DBI_max_rows from the result set. @@ -1271,10 +1392,12 @@ sub db_format_results my @col_length; my @table; my @headers; + my $z_user_data; + db_debug("ans: db_format_results() called"); return -1 unless defined $sth; - ($conn_local, $driver) = db_get_connection(); + ($conn_local, $driver, $z_user_data) = db_get_connection(); # Check if the NUM_OF_FIELDS is > 0. # In mysql a COMMIT does not provide a result set. @@ -1302,7 +1425,12 @@ sub db_format_results # a way to check the maximum length of an array without checking # every entry which we are already doing here. foreach my $col ( @{$row} ) { - $temp_length = length((defined($col)?$col:"")); + #$temp_length = length((defined($col)?$col:"")); + # For some reason the above can sometimes return the wrong length. + # The following two lines fix that problem + my $xstr = $col; + $temp_length = length((defined($col)?$xstr:"")); + $col_length[$i] = ( $temp_length > $col_length[$i] ? $temp_length : $col_length[$i] ); if ( $col_max_width > 0 ) { # $col_max_width is set via g:dbext_DBI_max_column_width @@ -1345,6 +1473,21 @@ sub db_format_results } } + #@dbms_output = $conn_local->func( 'dbms_output_get' ); + my @times_dbms_output = ( scalar localtime ); + if ($z_user_data->{z_srv_out_enabled}) { + eval { @dbms_output = fetch_dbms_output ($conn_local); }; + unshift @dbms_output, "query started: " . $times_dbms_output[0]; + push @times_dbms_output, scalar localtime; + push @dbms_output, "query finished: " . $times_dbms_output[1]; + if (defined ($@) and length ($@)) { + my $err_str_from_eval = 'eval failed: '.$@; + db_debug($err_str_from_eval); + @dbms_output = ($err_str_from_eval); + } + db_debug('db_format_results:DbOu count:'.scalar(@dbms_output)); + } + # db_echo(Dumper($sth)); $sth->finish; @@ -1404,10 +1547,14 @@ sub db_format_array() # blank padding each string. # Add an additional 3 spaces between columns. foreach my $col2 ( @{$row2} ) { + # ans: show NULL's as $val = (defined($col2)?$col2:"NULL"); + #$val = (defined($col2)?$col2:""); + # Remove any unprintable characters #$val =~ tr/\x80-\xFF/ /d; - $val =~ tr/\x80-\xFF/ /; + # ans: this prevent unicode utf8 symbols from being displayed in first place + #$val =~ tr/\x80-\xFF/ /; # Remove the NULL character since Vim will treat this as # the end of the line # For more of these see: @@ -1457,7 +1604,7 @@ sub db_print_results } db_set_vim_var('g:dbext_dbi_msg', ''); - db_debug("db_print_results: Using format: $format"); + db_debug("db_print_results: Using format: $format, last_line=$last_line"); if ( $format eq "horizontal" ) { # Print column names foreach my $row2 ( @result_headers ) { @@ -1485,8 +1632,14 @@ sub db_print_results $line .= '-' x $result_col_length[$i].$col_sep_vert; $i++; } - db_vim_print($last_line, $line); - $last_line++; + # ans: here it is "waschechtes" bug because everything goes lost otherwise unless we have some non-zero length headers string + if ($line ne "") { + db_vim_print($last_line, $line); + $last_line++; + db_debug("db_print_results: printed line <$line> as underlines of column headers, last_line=$last_line"); + } else { + db_debug("db_print_results: skipped printing empty line <$line> as underlines of column headers, last_line=$last_line"); + } # Print each row foreach my $row3 ( @result_set ) { @@ -1539,7 +1692,38 @@ sub db_print_results } } } + db_debug("db_print_results: before last_line(...rows...), last_line=$last_line"); db_vim_print($last_line, "(".scalar(@result_set)." rows)"); + if (@dbms_output) { + $last_line++; + db_debug("db_print_results: \@dbms_output size: ".scalar(@dbms_output)); + for my $dol0 (@dbms_output) { + # needed due to (DBI?) encoding problems when receiving from oracle with plsql + # in/out table, see https://github.com/ant0sha/dbext.vim/commit/b83befafd0fdc45f683aca1fa89b52ab959e8277 + my $dol = decode('utf8', ($dol0 // '')); + db_debug("db_print_results: printed dbms_output[i] (raw non-splitted) : ".$dol); + #chomp($dol); + + # we try to overtake the line number from the "printer" method + $last_line += db_vim_print($last_line, $dol); + #$last_line++; + } + if ($debug) { + db_debug("db_print_results: printed dbms_output: " . join ("\n", map { $_ // '' } @dbms_output)) + } + @dbms_output = (); + $last_line += db_vim_print($last_line, "printed at: " . scalar localtime); + } else { + $last_line++; + my ($connection_ignored, $driver_ignored, $z_user_data) = db_get_connection(); + if ($z_user_data->{z_srv_out_enabled}) { + db_vim_print($last_line, "dbms_output is empty."); + db_debug("db_print_results: dbms_output is empty."); + } else { + db_vim_print($last_line, "ACHTUNG: dbms_output is OFF."); + db_debug("db_print_results: dbms_output is OFF."); + } + } db_debug("db_print_results: returning 0"); return 0; } @@ -1742,6 +1926,241 @@ sub db_results_list return 0; } + +sub db_enable_srv_out +{ + + # TODO: refactor/publish related automated test case for dbms_output, which + # can be called like this: + # + # vim -c 'source \ + # Perl_Oracle_DBD_dbms_output/dbms_output_test_case_1.vim|call \ + # Test_Case_1()' + + my ($conn_local, $driver, $z_user_data) = db_get_connection(); + if ($z_user_data->{z_srv_out_enabled}) { + db_debug("skip of already enabled server output"); + } + else { + $conn_local->func( 1000000, 'dbms_output_enable' ); + $z_user_data->{z_srv_out_enabled} = 1; + } + + #db_set_vim_var('g:dbext_dbi_msg', 'A request_type must be specified'); + #db_set_vim_var('g:dbext_dbi_result', -1); +} + +sub db_disable_srv_out +{ + my ($conn_local, $driver, $z_user_data) = db_get_connection(); + if ($z_user_data->{z_srv_out_enabled}) { + my $stmt = $conn_local->prepare_cached( 'begin dbms_output.disable; end;' ); + $stmt->execute(); + $z_user_data->{z_srv_out_enabled} = 0; + } else { + db_debug("skip of already disabled server output"); + } + + #db_set_vim_var('g:dbext_dbi_msg', 'A request_type must be specified'); + #db_set_vim_var('g:dbext_dbi_result', -1); +} + +db_set_vim_var('g:loaded_dbext_dbi_msg', 'db_ora_extract_ddl'); +# Used to extract DDL and show dbms_output +# like lists of tables, columns, stored procedures +# and so on. +sub db_ora_extract_ddl +{ + my $object_name_like = shift; + my $level; + my $err; + my $msg; + my $state; + + # $debug = db_is_debug(); + if ( length($object_name_like) == 0 ) { + db_set_vim_var('g:dbext_dbi_msg', 'A object_name_like must be specified'); + db_set_vim_var('g:dbext_dbi_result', -1); + return -1; + } + if ( ! db_is_connected() ) { + db_debug("You must connect first"); + db_set_vim_var('g:dbext_dbi_msg', 'You are not connected to a database'); + db_set_vim_var('g:dbext_dbi_result', -1); + return -1; + } + + db_debug("object_name_like=$object_name_like"); + + my $sql = qq( + declare + vObjNameLike varchar2(200) := '$object_name_like'; + iNL integer; ddl CLOB; + part varchar2(32767); sNL varchar2(20) := CHR(10); + type str2num is table of number index by varchar2(100); + vTblLen str2num; + procedure l(s varchar2) is begin dbms_output.put_line(s); end; + begin + <> + for viRec in (select * from user_objects where object_name like upper(vObjNameLike)) loop + ddl := DBMS_METADATA.GET_DDL(viRec.object_type, viRec.object_name); + exit FOR_ALL_OBJ_LIKE; + end loop; + iNL := 1; + while (iNL > 0) loop + iNL := instr(ddl, sNL); + if (iNL <> 0) then + -- explanations to fix with "iNL-1" below: + --begin -- that produces NO empty lines - dbms_output.put_line simply do nothing if NL is single char in the string it gets! + -- dbms_output.put_line(CHR(10)); + -- dbms_output.put_line('aaa'); + -- dbms_output.put_line(CHR(10)); + -- dbms_output.put_line('bbb'); + --end;; + --begin -- that produces empty lines just fine + -- dbms_output.put_line(null); + -- dbms_output.put_line('aaa'); + -- dbms_output.put_line(null); + -- dbms_output.put_line('bbb'); + --end;; + part := substr(ddl, 1, iNL-1); ddl := substr(ddl, iNL+1); + else + part := ddl; ddl := null; + end if; + l(part); + iNL := instr(ddl, sNL); + end loop; + l(ddl); + + + declare + len number; nm varchar2(100); + vStr varchar2(32767); + -- + vObjNameLikeIndex varchar2(30) := vObjNameLike; + type str2num is table of number index by varchar2(100); + vTblLen str2num; + vIdxFound boolean := false; + begin + vTblLen('INDEX_NAME') := length('INDEX_NAME'); + vTblLen('UNIQUENESS') := length('UNIQUENESS'); + vTblLen('LAST_ANALYZED') := length('LAST_ANALYZED'); + vTblLen('NUM_ROWS') := length('NUM_ROWS'); + vTblLen('CONSTRAINT_INDEX') := length('CONSTRAINT_INDEX'); + for viIdx in (select * from user_indexes where upper(table_name) like upper(vObjNameLikeIndex)) loop + len := length(viIdx.INDEX_NAME); nm := 'INDEX_NAME'; + if (len > vTblLen(nm)) then vTblLen(nm) := len; end if; + len := length(viIdx.UNIQUENESS); nm := 'UNIQUENESS'; + if (len > vTblLen(nm)) then vTblLen(nm) := len; end if; + len := length(viIdx.LAST_ANALYZED); nm := 'LAST_ANALYZED'; + if (len > vTblLen(nm)) then vTblLen(nm) := len; end if; + len := length(viIdx.NUM_ROWS); nm := 'NUM_ROWS'; + if (len > vTblLen(nm)) then vTblLen(nm) := len; end if; + len := length(viIdx.CONSTRAINT_INDEX); nm := 'CONSTRAINT_INDEX'; + if (len > vTblLen(nm)) then vTblLen(nm) := len; end if; + end loop; + for viIdx in (select * from user_indexes where upper(table_name) like upper(vObjNameLikeIndex)) loop + if (not vIdxFound) then + -- first row + dbms_output.put_line('Indexes found:'); + vStr := + rpad('INDEX_NAME', vTblLen('INDEX_NAME'), ' ') + || ' ' || rpad('UNIQUENESS', vTblLen('UNIQUENESS'), ' ') + || ' ' || rpad('LAST_ANALYZED', vTblLen('LAST_ANALYZED'), ' ') + || ' ' || rpad('NUM_ROWS', vTblLen('NUM_ROWS'), ' ') + || ' ' || rpad('CONSTRAINT_INDEX', vTblLen('CONSTRAINT_INDEX'), ' ') + ; + dbms_output.put_line(vStr); + vStr := + rpad('=', vTblLen('INDEX_NAME'), '=') + || ' ' || rpad('=', vTblLen('UNIQUENESS'), '=') + || ' ' || rpad('=', vTblLen('LAST_ANALYZED'), '=') + || ' ' || rpad('=', vTblLen('NUM_ROWS'), '=') + || ' ' || rpad('=', vTblLen('CONSTRAINT_INDEX'), '=') + ; + dbms_output.put_line(vStr); + end if; + vIdxFound := true; + + dbms_output.put_line( + rpad(viIdx.INDEX_NAME, vTblLen('INDEX_NAME'), ' ') + || ' ' || rpad(viIdx.UNIQUENESS, vTblLen('UNIQUENESS'), ' ') + || ' ' || rpad(viIdx.LAST_ANALYZED, vTblLen('LAST_ANALYZED'), ' ') + || ' ' || rpad(viIdx.NUM_ROWS, vTblLen('NUM_ROWS'), ' ') + || ' ' || rpad(viIdx.CONSTRAINT_INDEX, vTblLen('CONSTRAINT_INDEX'), ' ') + ); + + end loop; + end; + + --dbms_output.put_line('gesmtlaenge='||length(ddl)||', iNL='||iNL); + exception when others then + l('exception happened'); + declare + emsg varchar2(32767); + begin + emsg := dbms_utility.format_error_backtrace; + l(emsg); + end; + end; + ); + + db_debug("sql=$sql"); + + my ($conn_local, $driver, $z_user_data) = db_get_connection(); + if ($z_user_data->{z_srv_out_enabled}) { + db_debug("skip of already enabled server output"); + } + else { + db_enable_srv_out; + } + + #($conn_local, $driver) = db_get_connection(); + my $sth = undef; + + eval { + $sth = $conn_local->prepare( $sql ); + }; + + if ($@) { + db_debug("db_ora_extract_ddl statement error for object_name_like $object_name_like\n".db_escape($@)); + db_set_vim_var('g:dbext_dbi_msg', 'Invalid statement for object_name_like:'.$object_name_like.":".db_escape($@)); + db_set_vim_var('g:dbext_dbi_result', -1); + return -1; + } + if ( defined($sth) ) { + db_debug("db_ora_extract_ddl statement is defined"); + $sth->execute; + ( $level, $err, $msg, $state ) = db_check_error($driver); + if ( ! $msg eq "" ) { + $msg = "$level. DBcate:".(($level ne "I")?"SQLCode:$err:":"").$msg.(($state ne "")?":$state":""); + db_set_vim_var('g:dbext_dbi_msg', $msg); + if ( $level eq "E" ) { + db_set_vim_var('g:dbext_dbi_result', -1); + db_debug("db_catalogue:$msg - exiting"); + return -1; + } + } + db_format_results( $sth ); + } else { + db_set_vim_var('g:dbext_dbi_result', -1); + ( $level, $err, $msg, $state ) = db_check_error($driver); + if ( ! $msg eq "" ) { + $msg = "$level. DBcats:".(($level ne "I")?"SQLCode:$err:":"").$msg.(($state ne "")?":$state":""); + db_set_vim_var('g:dbext_dbi_msg', $msg); + if ( $level eq "E" ) { + db_set_vim_var('g:dbext_dbi_result', -1); + db_debug("db_catalogue:$msg - exiting"); + return -1; + } + } + db_debug("db_catalogue statement failed:".$DBI::err.":".db_escape($DBI::errstr)); + return -1; + } + + return 0; +} + db_set_vim_var('g:loaded_dbext_dbi_msg', 'db_catalogue'); # Used to query the DBI catalogue and return metadata # like lists of tables, columns, stored procedures diff --git a/doc/dbext.txt b/doc/dbext.txt index 0e5b6e4..fc3568b 100644 --- a/doc/dbext.txt +++ b/doc/dbext.txt @@ -1,4 +1,4 @@ -*dbext.txt* For Vim version 7.0. Last change: 2016 Jan 03 +*dbext.txt* For Vim version 7.0. Last change: 2017 Nov 17 VIM REFERENCE MANUAL @@ -7,7 +7,7 @@ Peter Bagyinszki Database extension plugin (dbext.vim) manual - dbext.vim version 23.00 + dbext.vim version 26.00 For instructions on installing this file, type :help add-local-help @@ -17,7 +17,8 @@ Homepage: http://vim.sourceforge.net/script.php?script_id=356 *dbext* *dbext.vim* *db_ext* *db_ext.vim* *database-extension* *pgsql* *mysql* *asa* *ase* *ingres* *interbase* *sqlite* *sqlsrv* *ora* *oracle* -*db2* *DBI* *ODBC* *sqlanywhere* *firebird* +*db2* *DBI* *ODBC* *sqlanywhere* *ultralite* *hana* *firebird* +*CrateIO* *RDBMS* 1. Getting Started |dbext-getting-started| 2. Whats New |dbext-new| @@ -29,7 +30,7 @@ Homepage: http://vim.sourceforge.net/script.php?script_id=356 5.3 Database Specific Options |dbext-configure-options| 5.4 DB2 Modes |dbext-configure-db2| 5.5 DBI Installation |dbext-configure-dbi| -6. Mappings and commands |dbext-mappings| +6. Mappings and commands |dbext-maps-commands| 7. Adding new database types |dbext-newdb| 8. Prompting for input parameters |dbext-prompting| 9. Setting up connection information |dbext-connect| @@ -50,7 +51,7 @@ Homepage: http://vim.sourceforge.net/script.php?script_id=356 14.1 Using filetype support |dbext-filetypes-using| 14.2 Adding new filetypes |dbext-filetypes-adding| 15. Using SQL History |dbext-history| -16. Open Source |dbext-sourceforge| +16. Job Support |dbext-job| 17. Tutorial |dbext-tutorial| 17.1 Creating the connection |dbext-tutorial-connection| 17.2 The result buffer |dbext-tutorial-results| @@ -117,6 +118,103 @@ David Fishburn ============================================================================== 2. What's New *dbext-new* +Version ??.?? ( ??, YYYY) + + New Features + ------------ +- :DBXtractDdl - new command (oracle+DBI specific), which uses dbms_output to + extract (and display) DDL of requested DB object. +- :DBEnableSrvOut - new command (oracle+DBI specific), which enables + dbms_output to be collected by oracle for our session and as well enables + fetching of collected output so far after every PL/SQL or SQL query. NOTE: + dbms_output is enabled by default. +- :DBDisableSrvOut - new command (oracle+DBI specific), which disables + dbms_output to be collected by oracle for our session and as well disables + fetching +- empty lines in output coming from the DB are now forced to stay - otherwise + output of DDL (via dbms_output) is not useable + + Bug Fixes + --------- + +- dbext_dbi_debug - changed spelling to use 'g:' prefix +- dbext_dbi.vim - several enhancements for the case of non-ASCII output + - explicitly asking perl to use UTF8 in both source code and input/output + encoding + - get rid of $val =~tr/\x80-\xFF/ /; which replaced all non-ASCII things + with blanks +- dbext_dbi.vim - fixed bug in db_print_results() if empty line happened in + the non-expected place +- FuzzyFinder badly interact with dbext in that it creates global variable + FuzzyFinderMode. Filter out this variable during check for upgrade of old + profile. + +Version 26.00 (Nov 17, 2017) + + New Features + ------------ + - New option g:dbext_default_job_show_msgs to control whether information + messages are displayed when starting and stop jobs. Regular status + messages continue to display. + - New option g:dbext_default_job_pipe_regex to control how to identify + and strip piped in files and specify it separately via job options. + - Renamed option g:dbext_default_use_jobs to g:dbext_default_job_enable + to better support tab completion for the DBSetOption command. + + Bug Fixes + --------- + - Also check for 'timers' when checking for job support. + - Revamped job support. Tested on Windows, Linux and OSX. Works on + long running and very short jobs where it was erratic on first release. + +Version 25.00 (Jan 30, 2017) + + New Features + ------------ + - Added support for using Vim's Job / Channel feature for asynchronous + execution. If the job fails to start, the usual synchronous way + of launching the process happens (after displaying a message). + The main purpose for this feature is to allow you to continue + editing your files while waiting for the results from SQL + statements to complete. Prior to Vim's Job feature, this + was a synchronous process which could interrupt your development + needlessly. + - New commands DBJobStatus, DBJobStop, DBJobTimerStart, DBJobTimerStop. + - New options g:dbext_default_use_jobs defaults to 1 (enabled), + g:dbext_default_job_status_update_ms defaults to 2000. + + Bug Fixes + --------- + - None + +Version 24.00 (Sept 30, 2016) + + New Features + ------------ + - Added missing menu items for Execute All (sea), Execute Line (sel), + Execute Previous Visual Selection (sep) (pull #18) (Raphaël Bade) + - When using ?s as input parameters, these can now be remembered + and used when saving previous values when executing the query. + - Additional examples of connecting to Oracle using DBI/ODBC + without a TNSNAMES entry. + - Strip off ending cmd terminators for the DBI database types + as some databases (Oracle) complain about them (Bruce Hunsaker). + - For native Oracle connections, when retrieving database objects + (tables, procedures, columns, ...) ignore case (Dimitri Belov). + + Bug Fixes + --------- + - For Java, CSharp, JSP, HTML and JavaScript files, remove line + continuation (backslash) characters before sending request to database. + - Better error handling for the DBI / ODBC databases (Michael Graham, + Bruce Hunsaker, Dimitri Belov, Will Wyatt). + - The @ask[bg] parameter was not supported properly in later + releases (Richard Tsai, Nan Jia). + - Vim error - Invalid range reported for the SQL history (Bruce Hunsaker). + - DBI / ODBC CommitOnDisconnect driver parameter is not supported by all + drivers, handle cleanly (Dimitri Belov). + - DBI / ODBC Disconnect reported a Vim error (Dimitri Belov). + Version 23.00 (Dec 30, 2015) New Features @@ -983,11 +1081,11 @@ Version 2.01 (Jul 22, 2004) - New functionality for better integration with the Intellisense SQL plugin. - Ability to change the title of the window/buffer. - Integrated Login support for windows. - - Added new functionality to these commands: |dbext-mappings| + - Added new functionality to these commands: DBGetOption DBSetOption DBExecRangeSQL - - Added 4 new options: |dbext-configure-variables| + - Added 4 new options: replace_title custom_title use_tbl_alias @@ -1033,10 +1131,11 @@ Version 2.00 (Jul 11, 2004) Oracle Oracle Rdb on an Open VMS Node SQLite - Sybase Adaptive Server Anywhere + Sybase Adaptive Server Anywhere (SA / ASA) Sybase UltraLite - Sybase Adaptive Server Enterprise + Sybase Adaptive Server Enterprise (ASE) SAP HANA + CrateIO When using a Perl enabled Vim ( :echo has('perl') ): DBI ODBC @@ -1784,6 +1883,41 @@ Version 2.00 (Jul 11, 2004) specify. > :DBSetOption filetype=sql let g:dbext_default_profile_test = 'type=ASA:user=DBA:passwd=SQL:filetype=sql' + dbext_default_job_enable +< Default: 1 + New feature to version 26.0. Useful when using any of the database + types that must spawn an executable (i.e. not DBI or ODBC). + Vim 8 introduced a new Job / Channel feature which allows asynchronous + execution of background jobs. So, if executing a query (without jobs) + you can no longer edit your code until the query finishes execution + and displays a result. When using this feature (:echo has('job')) the + query will take place in the background allowing you to continue + editing your code until it completes. > + :DBSetOption job_enable=1 + let g:dbext_default_job_enable = 1 + dbext_default_job_status_update_ms +< Default: 2000 + New feature to version 25.0. When running a SQL statement via + jobs, a status is displayed to the user every 2 seconds. > + :DBSetOption job_status_update_ms=5000 + let g:dbext_default_job_status_update_ms = 5000 + dbext_default_job_show_msgs +< Default: 1 + New feature to version 25.0. When running a SQL statement via + jobs, a status is displayed to the user every 2 seconds. These + are echomsgs, rather than messages displayed in the result buffer. + Version 26 adds additional information into the result buffer + so this can allow you to reduce some messages. > + :DBSetOption job_show_msgs=0 + let g:dbext_default_job_show_msgs = 0 + dbext_default_job_pipe_regex +< Default: \%(@\|<\) + New feature to version 26.0. For the database types which run a + command from a shell which pipe the SQL statements into the utility + using a pipe command, then when running a job, the input file is + specified in a different way. This allows a configurable way of + identifing and removing the pipe when setting up the job. > + let g:dbext_default_job_pipe_regex = '\%(@\|<\)' < @@ -2152,7 +2286,7 @@ Version 2.00 (Jul 11, 2004) quit ============================================================================== -6. Mappings and commands *dbext-mappings* +6. Mappings and commands *dbext-maps-commands* Default visual/normal mode mappings (|:vmap|,|:nmap|): @@ -2418,6 +2552,22 @@ Version 2.00 (Jul 11, 2004) which is especially useful for one or two rows of output. This will toggle the current display. + DBJobStatus - Shows the status of the currently running job. + DBJobStop - Allows the user to terminate the currently running + job. Command completion has been enabled for this + to list the various options Vim supports to cancel + jobs. Though Vim's defaults are usually + sufficient. + DBJobTimerStop - When a job is started a timer runs which indicates + to the user a job is in progress and how many + milliseconds it has been running. Stopping the + timer does not does not stop the job it merely + stops notifying the user. + DBJobTimerStart - Added for completeness. Running a SQL statement + which starts a job automatically starts a status + timer. This command will allow you to restart the + timer, assuming you had stopped it using + DBJobTimerStop. ============================================================================== @@ -2486,7 +2636,7 @@ Version 2.00 (Jul 11, 2004) INTO p1, p2 FROM customer WHERE name = @name - AND address = ? + AND country = ? AND phone = :phone_nbr AND email = 'bob@something.com' AND c2 = 'property:name'; @@ -2503,13 +2653,24 @@ Version 2.00 (Jul 11, 2004) 4. When prompted for a value for @name, I would enter 'Homer' (including the single quotes). 5. When prompted for a value for ? it will indicate which ? this is, by - counting them and prompting you for a value for ? number 6. + counting them and prompting you for a value for ? number 1. Enter + a value of 'Canada' (again, including quotes). + The resulting query which is sent to the database will be: > + SELECT varexists('@dave'), column2 + FROM customer + WHERE name = 'Homer' + AND country = 'Canada' + AND phone = :phone_nbr + AND email = 'bob@something.com' + AND c2 = 'property:name'; +< To modify what variables that are searched for consider the following example: > INSERT INTO sync_log ( user_id, table_name, line ) VALUES( i_user, i_table_name, i_str ); - @@ -2643,8 +2804,10 @@ To specify the type of database you connect to most often, you can place the let g:dbext_default_profile_ORA = 'type=ORA:srvname=ffs42ga:user=john:passwd=whatever' " Oracle SQL Connect URL string (notice the : is escaped for the port option) let g:dbext_default_profile_ORA_URL = 'type=ORA:srvname=//localhost\:3333/instance_name:user=scott:passwd=tiger' - " Different form of Oracle server name + " These 2 examples connect without using an entry in TNSNAMES let g:dbext_default_profile_ORA_Extended = 'type=ORA:user=scott:passwd=tiger:srvname=(description=(address=(protocol=TCP)(host=localhost)(port=1521))(connect_data=(server=dedicated)(service_name=10gR2)))' + let g:dbext_default_profile_ORA_Extended_DBI = 'type=DBI:user=system:passwd=oracle:driver=Oracle:conn_parms=SID=orcl12c;HOST=localhost;PORT=1521' + " PostgreSQL let g:dbext_default_profile_PG = 'type=PGSQL:user=postgres' @@ -2661,6 +2824,9 @@ To specify the type of database you connect to most often, you can place the " SQLite let g:dbext_default_profile_POPFile = 'type=SQLITE:SQLITE_bin=C:\Programs\POPFile\sqlite.exe:dbname=C:\Programs\POPFile\popfile.db' + let g:dbext_default_profile_SQLite = 'type=SQLITE:SQLITE_bin=C:\download\OpenSrc\Databases\SQLite\sqlite3.exe:dbname=\vim\test\dbext\sqlite\test.db' + let g:dbext_default_profile_SQLite_DBI = 'type=DBI:driver=SQLite:conn_parms=dbname=\vim\test\dbext\sqlite\test.db' + let g:dbext_default_profile_SQLite_diff_cmdT = 'type=SQLITE:dbname=C:\vim\test\dbext\sqlite\test.db:cmd_terminator=~' " ULTRALITE let g:dbext_default_profile_UL_CustDB = 'type=ULTRALITE:user=dba:passwd=sql:dbname='.expand('$SQLANYSAMP11\ultralite\custdb\custdb.udb') @@ -3089,11 +3255,46 @@ To specify the type of database you connect to most often, you can place the for more details. ============================================================================== -16. Open Source *dbext-sourceforge* +16. Job Support *dbext-job* + + If your Vim supports Jobs (:echo has('job')), by default when using a + database type other than DBI or ODBC, dbext will create a job to run + the SQL statement against the database. The advantage of using jobs + is they are asynchronous. This means you can continue to edit your + buffer, while waiting for the SQL statement to complete and show the + results. + + Jobs cannot be used for the DBI and ODBC interfaces, as those use the + built in Perl support of Vim and do not run external to Vim. + + Jobs cannot be used when not using the result buffer option + (g:dbext_default_use_result_buffer = 0). This is automatically enabled + for example when using the OMNI SQL completion functions provided by Vim's + SQL filetype plugin in conjunction with dbext (|dbext-completion|). + + Only one dbext background job can be running at one time. + + If a job fails to start, it will fall back to synchronous execution. + + There are a few additional commands available to manage the jobs created: > + DBJobStatus +< Shows the status of the currently running job. > + DBJobStop +< Allows the user to terminate the currently running job. + Command completion has been enabled for this to list the various + options Vim supports to cancel jobs. Though Vim's defaults are + usually sufficient. > + DBJobTimerStop +< When a job is started a timer runs which indicates to the user + a job is in progress and how many milliseconds it has + been running. Stopping the timer does not does not stop the job + it merely stops notifying the user. > + DBJobTimerStart +< Added for completeness. Running a SQL statement which starts a job + automatically starts a status timer. This command will allow you to + restart the timer, assuming you had stopped it using DBJobTimerStop. + - dbext is now an open source project found at: > - https://sourceforge.net/projects/dbext/ -< ============================================================================== 17. Tutorial *dbext-tutorial* @@ -4147,6 +4348,52 @@ To specify the type of database you connect to most often, you can place the read c:\temp\my.sql; read /temp/my.sql; < + **DDL extraction - oracle specific** + + Given you have a Table like this: > + create table tmp_for_dbext ( + c1 number, + s1 varchar2(20), + d1 date + ); +< + You can use > + :DBXtractDdl tmp_for_dbext +< + to see the DDL of that table: > + CREATE TABLE "CAQCSM"."TMP_FOR_DBEXT" + ( "C1" NUMBER, + "S1" VARCHAR2(20 CHAR), + "D1" DATE + ) SEGMENT CREATION DEFERRED + PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 + NOCOMPRESS LOGGING + TABLESPACE "SOME_TABLESPACE" NO INMEMORY +< + (the output is coming as-is from DBMS_METADATA.GET_DDL() package and + displayed to the user with dbms_output oracle feature. Only for tables + additional query is used to fetch index data from user_indexes table). This + works as well for triggers/packages/types/views. + + **Working with DBMS_OUTPUT - oracle specific** + + After issue of: > + :DBEnableSrvOut +< + + dbext will fetch collected dbms_output on server, e.g. if you execute for + example following PLSQL block: > + begin for i in 1..3 loop dbms_output.put_line('iteration #'||i); end loop; end;; +< + you will see as expected: > + iteration #1 + iteration #2 + iteration #3 +< + After issue of: > + :DBDisableSrvOut +< + no output at all will come from PLSQL block above. Summary ------- diff --git a/plugin/dbext.vim b/plugin/dbext.vim index b227408..0738d67 100644 --- a/plugin/dbext.vim +++ b/plugin/dbext.vim @@ -1,11 +1,11 @@ -" dbext.vim - Commn Database Utility +" dbext.vim - Common Database Utility " Copyright (C) 2002-16, Peter Bagyinszki, David Fishburn " --------------------------------------------------------------- -" Version: 23.00 +" Version: 26.00 " Maintainer: David Fishburn " Authors: Peter Bagyinszki " David Fishburn -" Last Modified: 2015 Dec 29 +" Last Modified: 2017 Oct 10 " Based On: sqlplus.vim (author: Jamis Buck) " Created: 2002-05-24 " Homepage: http://vim.sourceforge.net/script.php?script_id=356 @@ -40,7 +40,7 @@ if v:version < 702 echomsg "dbext: Version 22.00 or higher requires Vim7.2 or higher. Version 21.00 can stil be used with Vim 7.1 and lower." finish endif -let g:loaded_dbext = 2300 +let g:loaded_dbext = 2600 " Turn on support for line continuations when creating the script let s:cpo_save = &cpo @@ -70,16 +70,24 @@ if !exists('g:dbext_map_or_cmd') endif endif +if !exists('g:dbext_default_use_jobs') + let g:dbext_default_use_jobs = 1 +endif + " Commands {{{ command! -nargs=+ DBExecSQL :call dbext#DB_execSql() command! -nargs=+ DBExecSQLTopX :call dbext#DB_execSqlTopX() -command! -nargs=0 DBConnect :call dbext#DB_connect() +command! -nargs=* DBConnect :call dbext#DB_connect() command! -nargs=* DBDisconnect :call dbext#DB_disconnect() command! -nargs=* DBDisconnectAll :call dbext#DB_disconnectAll() command! -nargs=0 DBCommit :call dbext#DB_commit() command! -nargs=0 DBRollback :call dbext#DB_rollback() command! -nargs=0 DBListConnections :call dbext#DB_getListConnections() command! -nargs=0 DBProfilesRefresh :call dbext#DB_buildLists() +command! -nargs=* -complete=customlist,DB_completeJobStop DBJobStop :call dbext#DB_jobStop() +command! -nargs=0 DBJobStatus :call dbext#DB_jobStatus() +command! -nargs=0 DBJobTimerStop :call dbext#DB_jobTimerStop() +command! -nargs=0 DBJobTimerStart :call dbext#DB_jobTimerStart() command! -range -nargs=0 DBExecRangeSQL ,call dbext#DB_execRangeSql() command! -nargs=+ Call :call dbext#DB_execSql("call " . ) command! -nargs=+ -complete=customlist,dbext#DB_completeTables Select :call dbext#DB_execSql("select " . ) @@ -151,6 +159,14 @@ if !exists(':DBDescribeTable') \ :call dbext#DB_describeTable() nmap