nvim-dbee icon indicating copy to clipboard operation
nvim-dbee copied to clipboard

[Feature Request] Run query under cursor

Open Rizhiy opened this issue 1 year ago • 6 comments

Rather than having to select a query using visual mode, it would be great if you could just call a function to automatically select the query under cursor and run it.

Tree sitter sql grammar has statement node which selects the whole query.

Rizhiy avatar Jun 09 '24 02:06 Rizhiy

I have created a custom keymap to do just that:

vim.api.nvim_create_autocmd({ "FileType" }, {
	desc = "On buffer enter with file type sql",
	group = vim.api.nvim_create_augroup("dbee", { clear = true }),
	pattern = { "sql" },
	callback = function()
		vim.keymap.set({ "n" }, "<leader>de", function()
			vim.api.nvim_feedkeys("vip", "n", false)
			local srow, scol, erow, ecol = require("dbee.utils").visual_selection()
			local selection = vim.api.nvim_buf_get_text(0, srow, scol, erow, ecol, {})
			local query = table.concat(selection, "\n")
			local command = string.format("Dbee execute %s", query)
			-- vim.print(command)
			vim.api.nvim_command(command)
		end, { desc = "[D]bee [e]xecute query under cursor" })
	end,
})

Two things to consider though:

  1. queries must be separated by an empty line
-- this will either run 2 queries or not work depending on the adapter I guess
select a from table;
select b from table_2;
-- this will run ok
select a from table;

select b from table_2;
  1. If you are on a .sql file and use this keybind it will open Dbee UI and run the selected query. But the query will not show up on Dbee editor

joao-alho avatar Aug 08 '24 15:08 joao-alho

@joao-alho I see you don't know that you shouldn't parse syntax using naive rules)

As I've said, you can and should do this using treesitter:

function M.get_query()
    local ts_utils = require("nvim-treesitter.ts_utils")
    local current_node = ts_utils.get_node_at_cursor()

    local last_statement = nil
    while current_node do
        if current_node:type() == "statement" then last_statement = current_node end
        if current_node:type() == "program" then break end
        current_node = current_node:parent()
    end

    if not last_statement then return "" end

    local srow, scol, erow, ecol = vim.treesitter.get_node_range(last_statement)
    local selection = vim.api.nvim_buf_get_text(0, srow, scol, erow, ecol, {})
    return table.concat(selection, "\n")
end

This should work properly with any valid query, even if it has queries next to it and blank lines inside, so allows you to use it with more complex queries. For example, all three queries will be extracted correctly from text below:

select name from table;
select two from three;
with items as (
	select name from table

	-- A comment
	where age > 20
)

select count(name) from items;

I haven't been working with databases recently, but might change it into a PR over the weekend.

Rizhiy avatar Aug 09 '24 00:08 Rizhiy

hey thanks a lot, for both the docs and the proper way of doing it.

joao-alho avatar Aug 09 '24 15:08 joao-alho

FWIW I personally find visual select + trigger to be a very nice way of doing this. I have the following setup;

  1. dbee
require("dbee").setup {
  editor = {
    mappings = {
      {
        action = "run_selection",
        key = "<C-M>",
        mode = "x",
        opts = { noremap = true, nowait = true, expr = true },
      },
    },
  },
}

This allows me to, for example, vip<cr> to execute the visual selection.

  1. as you've noted, vip isn't the right textobject for this. so i have nvim-treesitter-textobjects installed with the following sql textobjects;
(statement) @block.outer

[
 (select)
 (from)
 (join)
 (where)
 (group_by)
] @block.inner

and then in my treesitter config;

require("nvim-treesitter.configs").setup {
  textobjects = {
    select = {
      enable = true,
      keymaps = {
        ["ab"] = "@block.outer",
        ["ib"] = "@block.inner",
      },
    },
  },
}

Now vab<cr> accomplishes the same thing as @Rizhiy's snippet.

Again, doesn't answer your question specifically but thought I'd share regardless :)

willruggiano avatar Sep 04 '24 20:09 willruggiano

Fox executing the statements under the cursor one could do

            mappings = {
                {
                    key = "<C-g>",
                    mode = "i",
                    action = function()
                        local ts = require("vim.treesitter")
                        local current_node = ts.get_node()

                        while current_node and current_node:type() ~= "statement" do
                            current_node = current_node:parent()
                        end

                        if not current_node then
                            return
                        end
                        local srow, scol, erow, ecol = vim.treesitter.get_node_range(current_node)
                        local selection = vim.api.nvim_buf_get_text(0, srow, scol, erow, ecol, {})

                        if not selection or #selection == 0 then return end
                        selection = vim.tbl_map(fn.trim_empty_space, selection)

                        local editor = require("dbee.api.state").editor()
                        local conn = editor.handler:get_current_connection()
                        if not conn or not conn.id then return end

                        local query = table.concat(selection, " ")
                        local call = editor.handler:connection_execute(conn.id, query)
                        return editor.result:set_call(call)
                    end
                },
            },

asmodeus812 avatar Sep 07 '24 13:09 asmodeus812

hi @Rizhiy sorry for the delayed answer - I propose merging this default into master in #203. I'd appreciate your input on this if you've time. Thanks!

edit: added treesitter, so now it should support more complex statement, e.g. CTEs etc.

MattiasMTS avatar Mar 30 '25 17:03 MattiasMTS