kndndrj / nvim-dbee

Interactive database client for neovim
GNU General Public License v3.0
755 stars 51 forks source link

[Feature Request] Run query under cursor #121

Open Rizhiy opened 3 months ago

Rizhiy commented 3 months ago

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.

joao-alho commented 1 month ago

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;


2. 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
Rizhiy commented 1 month ago

@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.

joao-alho commented 1 month ago

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

willruggiano commented 4 weeks ago

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 :)

asmodeus812 commented 3 weeks ago

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
                },
            },