From 10cc97e3f1d402293360b79e104eeacf5825c8a9 Mon Sep 17 00:00:00 2001 From: Jonas-Taha El Sesiy Date: Sat, 20 Sep 2025 23:09:06 -0700 Subject: [PATCH] Add support for Snowflake CLI alongside SnowSQL - Add g:db_adapter_snowflake_use_cli variable to toggle between tools - Switch to long cli args to simplify command invocation (same args for both) - Switch to csv completion for the same reason (snow cli doesn't support tsv) - Keep SnowSQL as default for backward compatibility - Add tests to ensure functionality Relates to https://github.com/tpope/vim-dadbod/pull/174 Signed-off-by: Jonas-Taha El Sesiy --- autoload/db/adapter/run_tests.vim | 11 ++++++ autoload/db/adapter/snowflake.vim | 50 ++++++++++++++++++++------ autoload/db/adapter/test_snowflake.vim | 40 +++++++++++++++++++++ doc/dadbod.txt | 2 ++ 4 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 autoload/db/adapter/run_tests.vim create mode 100644 autoload/db/adapter/test_snowflake.vim diff --git a/autoload/db/adapter/run_tests.vim b/autoload/db/adapter/run_tests.vim new file mode 100644 index 0000000..2a01256 --- /dev/null +++ b/autoload/db/adapter/run_tests.vim @@ -0,0 +1,11 @@ +source ../../db.vim +source ../url.vim +source ../adapter.vim +source snowflake.vim +source test_snowflake.vim + +call TestSnowflakeAdapterDefault() +call TestSnowflakeAdapterCLI() +call TestSnowflakeCompleteDatabase() + +:silent !echo "All tests passed" diff --git a/autoload/db/adapter/snowflake.vim b/autoload/db/adapter/snowflake.vim index 26a67fe..f1c6b77 100644 --- a/autoload/db/adapter/snowflake.vim +++ b/autoload/db/adapter/snowflake.vim @@ -1,17 +1,42 @@ +let s:use_cli = get(g:, 'db_adapter_snowflake_use_cli', 0) + +function! s:get_base() abort + return s:use_cli ? ['snow', 'sql'] : ['snowsql'] +endfunction + +function! s:get_env_var() abort + return s:use_cli ? 'SNOWFLAKE_PASSWORD' : 'SNOWSQL_PWD' +endfunction + +" Use long args as they match for both snowsql and snow cli +" ref: https://docs.snowflake.com/en/user-guide/snowsql-start#connection-parameters-reference +" ref: https://docs.snowflake.com/en/developer-guide/snowflake-cli/command-reference/sql-commands/sql#options +function! s:map_argv(url) abort + return db#url#as_argv(a:url, '--accountname ', '', '', '--username ', '','--dbname ') +endfunction + function! db#adapter#snowflake#interactive(url) abort let url = db#url#parse(a:url) - let cmd = (has_key(url, 'password') ? ['env', 'SNOWSQL_PWD=' . url.password] : []) + - \ ["snowsql"] + - \ db#url#as_argv(a:url, '-a ', '', '', '-u ', '','-d ') + let cmd = (has_key(url, 'password') ? ['env', s:get_env_var() . '=' . url.password] : []) + + \ s:get_base() + + \ s:map_argv(url) for [k, v] in items(url.params) - call add(cmd, '--' . k . '=' . v) + if s:use_cli + call add(cmd, '-D ' . k . '=' . v) + else + call add(cmd, '--' . k . '=' . v) + endif endfor return cmd endfunction function! db#adapter#snowflake#filter(url) abort - return db#adapter#snowflake#interactive(a:url) + - \ ['-o', 'friendly=false', '-o', 'timing=false'] + if s:use_cli + return db#adapter#snowflake#interactive(a:url) + ['--silent'] + else + return db#adapter#snowflake#interactive(a:url) + + \ ['-o', 'friendly=false', '-o', 'timing=false'] + endif endfunction function! db#adapter#snowflake#input(url, in) abort @@ -24,8 +49,13 @@ endfunction function! db#adapter#snowflake#complete_database(url) abort let pre = matchstr(a:url, '[^:]\+://.\{-\}/') - let cmd = db#adapter#snowflake#filter(pre) + - \ ['-o', 'header=false', '-o', 'output_format=tsv'] + - \ ['-q', 'show terse databases'] - return map(db#systemlist(cmd), { _, v -> split(v, "\t")[1] }) + if s:use_cli + let cmd = db#adapter#snowflake#filter(pre) + + \ ['--format', 'csv', '-q', 'show terse databases'] + else + let cmd = db#adapter#snowflake#filter(pre) + + \ ['-o', 'header=false', '-o', 'output_format=csv'] + + \ ['-q', 'show terse databases'] + endif + return map(db#systemlist(cmd), { _, v -> split(v, ",")[1] }) endfunction diff --git a/autoload/db/adapter/test_snowflake.vim b/autoload/db/adapter/test_snowflake.vim new file mode 100644 index 0000000..e21ef0a --- /dev/null +++ b/autoload/db/adapter/test_snowflake.vim @@ -0,0 +1,40 @@ +let s:test_url = 'snowflake://user:pass@account/database' + +function! TestSnowflakeAdapterDefault() + " Test default behavior uses snowsql + let cmd = db#adapter#snowflake#interactive(s:test_url) + call assert_equal('snowsql', cmd[0]) + call assert_true(index(cmd, '--accountname') >= 0) +endfunction + +function! TestSnowflakeAdapterCLI() + " Test with CLI enabled + let g:db_adapter_snowflake_use_cli = 1 + let cmd = db#adapter#snowflake#interactive(s:test_url) + call assert_equal('snow', cmd[0]) + call assert_equal('sql', cmd[1]) + call assert_true(index(cmd, '--accountname') >= 0) + unlet g:db_adapter_snowflake_use_cli +endfunction + +function! TestSnowflakeCompleteDatabase() + let url = 'snowflake://user@account/' + let result = db#adapter#snowflake#complete_database(url) + " Assume mock or check structure + call assert_true(type(result) == type([])) +endfunction + +function! TestSnowflakeCompleteDatabaseCSV() + " Test CSV parsing for completion + let original_systemlist = function('db#systemlist') + function! db#systemlist(cmd) + return ['database1,database2'] + endfunction + try + let url = 'snowflake://user@account/' + let result = db#adapter#snowflake#complete_database(url) + call assert_equal(['database2'], result) + finally + let db#systemlist = original_systemlist + endtry +endfunction diff --git a/doc/dadbod.txt b/doc/dadbod.txt index 6e3af02..a9a9cbc 100644 --- a/doc/dadbod.txt +++ b/doc/dadbod.txt @@ -202,6 +202,8 @@ Snowflake ~ Query parameters can be used to set additional command line options (e.g., `?warehouse=foo`). +Defaults to `snowsql`, set `let db_adapter_snowflake_use_cli = 1` to use +`snow` cli instead. *dadbod-sqlserver* SQL Server ~ >