From 0f7bc23433af3076d214440ed68ecd426d27509d Mon Sep 17 00:00:00 2001 From: Zhihao Wu Date: Thu, 1 Oct 2026 22:15:56 +0700 Subject: [PATCH] Fix LLM context queries for quoted table names --- CHANGELOG.md | 1 + litecli/packages/special/llm.py | 4 ++-- tests/test_llm_special.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d91317..375ce17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Bug Fixes +- Fix `\llm` failing to read tables or views whose names need quoting. - Expand `~` in configured log file paths before opening the log. ### Internal diff --git a/litecli/packages/special/llm.py b/litecli/packages/special/llm.py index e4391d7..ce65d67 100644 --- a/litecli/packages/special/llm.py +++ b/litecli/packages/special/llm.py @@ -389,7 +389,7 @@ def sql_using_llm( ORDER BY 1 """ click.echo("Preparing schema information to feed the llm") - sample_row_query = "SELECT * FROM {table} LIMIT 1" + sample_row_query = 'SELECT * FROM "{table}" LIMIT 1' log.debug(schema_query) cur.execute(schema_query) db_schema = "\n".join([x for (x,) in cur.fetchall()]) @@ -398,7 +398,7 @@ def sql_using_llm( cur.execute(tables_query) sample_data = {} for (table,) in cur.fetchall(): - sample_row = sample_row_query.format(table=table) + sample_row = sample_row_query.format(table=table.replace('"', '""')) cur.execute(sample_row) if cur.description is None: continue diff --git a/tests/test_llm_special.py b/tests/test_llm_special.py index b62a8b9..9c00e75 100644 --- a/tests/test_llm_special.py +++ b/tests/test_llm_special.py @@ -1,3 +1,4 @@ +from contextlib import closing from unittest.mock import patch import pytest @@ -5,6 +6,8 @@ import litecli.packages.special.llm as llm_module from litecli.packages.special.llm import USAGE, FinishIteration, handle_llm +from .utils import db_connection + @pytest.fixture(autouse=True) def enable_llm(monkeypatch): @@ -174,3 +177,29 @@ def test_llm_command_question_verbose(mock_sql_using_llm, mock_ensure_template, assert sql == "SELECT 42;" assert isinstance(duration, float) + + +@pytest.mark.parametrize("name", ["products", "products 1", "order", 'product"name', "catalog.products"]) +@pytest.mark.parametrize("object_type", ["table", "view"]) +@patch("litecli.packages.special.llm.run_external_cmd") +def test_sql_using_llm_quotes_table_and_view_names(mock_run_cmd, object_type, name): + response = "```sql\nSELECT 42;\n```" + mock_run_cmd.return_value = (0, response) + quoted_name = '"' + name.replace('"', '""') + '"' + + with closing(db_connection()) as connection: + cursor = connection.cursor() + if object_type == "table": + cursor.execute(f"CREATE TABLE {quoted_name} (name TEXT, quantity INTEGER)") + cursor.execute(f"INSERT INTO {quoted_name} VALUES ('widget', 1), ('gadget', 2)") + else: + cursor.execute(f"CREATE VIEW {quoted_name} AS SELECT 'widget' AS name, 1 AS quantity") + + result, sql, _ = llm_module.sql_using_llm(cursor, "Top products?") + + assert result == response + assert sql == "SELECT 42;" + mock_run_cmd.assert_called_once() + args = mock_run_cmd.call_args.args + sample_data = args[args.index("sample_data") + 1] + assert sample_data == str({name: [("name", "widget"), ("quantity", 1)]})