tpope / vim-dadbod

dadbod.vim: Modern database interface for Vim
https://www.vim.org/scripts/script.php?script_id=5665
3.75k stars 132 forks source link

Add ClickHouse Adapter #77

Closed gviamont closed 3 years ago

gviamont commented 3 years ago

Hello, I read in the guidelines that you have interest in adding more adapters. Recently I've been working with ClickHouse which is a columnar OLAP engine developed and used by Yandex. I absolutely love vim-dadbob and have used it quite a bit with Postgres/TImescaleDB. When I started working with ClickHouse I was looking to recreate the same experience I was able to enjoy with Postgres using dadbod, and fortunately ClickHouse supports the wire format used by MySQL client. The only thing needed to successfully communicate between mysql client and a ClickHouse server is to use the tcp protocol option.

Setting the protocol to tcp involves adding the --protocol=TCP option to the mysql invocation. I took a look at adapters/mysql.vim and noticed a potential placeholder for the protocol option on line 17. To be fair, I did not dig into the use of db#url#absorb_params there too deeply, but it seems like protocol may not be getting used. I did try adding ?protocol=tcp to the mysql URL that I'm using with the :DB command and even added some echom debug messaging of the key/value pairs in params to try to see what may be happening with that param to no avail. Admittedly I could be interpreting the code here very incorrectly (my vimscripting is not very strong).

In the end I was able to get the mysql client to work in dadbod by creating a new clickhouse.vim adapter in the adapter directory. A diff of the code below with mysql.vim should reveal that the only differences are adding " --protocol=tcp" to the mysql invocation, renaming the functions to contain "clickhouse" instead of "mysql" and similarly replacing url references of "mysql" to "clickhouse". This is may be a hacky way to support the adapter, and it might be cleaner to just add support for the optional --protocol option in the mysql adapter. Apologies in advance if that already works and I have misunderstood how to correctly use the mysql adapter. Thanks again for creating such a great plugin!

if exists('g:autoloaded_db_clickhouse')
  finish
endif
let g:autoloaded_db_clickhouse = 1

function! db#adapter#clickhouse#canonicalize(url) abort
  let url = substitute(a:url, '^clickhouse\d*:/\@!', 'clickhouse:///', '')
  " JDBC
  let url = substitute(url, '//address=(\(.*\))\(/[^#]*\)', '\="//".submatch(2)."&".substitute(submatch(1), ")(", "\\&", "g")', '')
  let url = substitute(url, '[&?]', '?', '')
  return db#url#absorb_params(url, {
        \ 'user': 'user',
        \ 'password': 'password',
        \ 'path': 'host',
        \ 'host': 'host',
        \ 'port': 'port',
        \ 'protocol': ''})
endfunction

function! s:command_for_url(url) abort
  let params = db#url#parse(a:url).params
  return 'mysql --protocol=TCP' .
        \ (has_key(params, 'login-path') ? ' --login-path=' . shellescape(params['login-path'])  : '') .
        \ db#url#as_args(a:url, '-h ', '-P ', '-S ', '-u ', '-p', '')
endfunction

function! db#adapter#clickhouse#interactive(url) abort
  return s:command_for_url(a:url)
endfunction

function! db#adapter#clickhouse#filter(url) abort
  return s:command_for_url(a:url) . ' -t'
endfunction

function! db#adapter#clickhouse#auth_pattern() abort
  return '^ERROR 104[45] '
endfunction

function! db#adapter#clickhouse#complete_opaque(url) abort
  return db#adapter#clickhouse#complete_database('clickhouse:///')
endfunction

function! db#adapter#clickhouse#complete_database(url) abort
  let pre = matchstr(a:url, '[^:]\+://.\{-\}/')
  let cmd = s:command_for_url(pre)
  let out = system(cmd, 'show databases')
  return split(out, "\n")[1:-1]
endfunction

function! db#adapter#clickhouse#tables(url) abort
  return split(system(s:command_for_url(a:url). ' -e "show tables"'), "\n")
endfunction
tpope commented 3 years ago

This is may be a hacky way to support the adapter, and it might be cleaner to just add support for the optional --protocol option in the mysql adapter.

Yep, and this should be easy to do: Just duplicate the login-path line and change it to use protocol.

Poking around a bit, while mysql does special case localhost, you can also do mysql://127.0.0.1/db to force a TCP connection. I'll still accept a ?protocol= patch as that's a bit cleaner.

tpope commented 3 years ago

You can also force protocol=TCP in .my.cnf/.mylogin.cnf, which might be helpful for some setups.