Repository: dbcli/litecli

Issue:
The describe command fails with 'near "(": syntax error'
Any use of the describe command results in a syntax error.

```
dev.db> \d auth_user
near "(": syntax error
```

This is due to passing a tuple to `format()` instead of the string arg directly.

```
    if arg:
        args = (arg,)
        query = """
            PRAGMA table_info({})
        """.format(args)  # <-- this should just be arg, as it was in the previous commit
    else:
        return list_tables(cur)
```


Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7bda87b..58f8192 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,14 +1,8 @@
-## Upcoming - TBD
-
-
-### Features
-
+## 1.12.4 - 2024-11-11
 
 ### Bug Fixes
 
-
-### Internal Changes
-
+* Fix the syntax error when `\d tbl` is used.
 
 ## 1.12.3  - 2024-09-10
 
@@ -17,7 +11,6 @@
 * Specify build system in `pyproject.toml`
 * Don't install tests
 
-
 ## 1.12.2  - 2024-09-07
 
 ### Bug Fixes
@@ -40,7 +33,6 @@
 
 * Fix the escape sequence warning.
 
-
 ## 1.11.0 - 2024-05-03
 
 ### Improvements
@@ -48,19 +40,16 @@
 * When an empty `\d` is invoked the list of tables are returned instead of an error.
 * Show SQLite version at startup.
 
-
 ### Bug Fixes
 
 * Support a single item in the startup commands in the config. (bug #176)
 
-
 ## 1.10.1 - 2024-3-23
 
 ### Bug Fixes
 
 * Do not crash at start up if ~/.config/litecli is not writeable. [#172](https://github.com/dbcli/litecli/issues/172)
 
-
 ## 1.10.0 - 2022-11-19
 
 ### Features
@@ -82,7 +71,6 @@
 * Skip internal indexes in the .schema output
   ([#170](https://github.com/dbcli/litecli/issues/170)).
 
-
 ## 1.9.0 - 2022-06-06
 
 ### Features
@@ -125,72 +113,70 @@
 
 ### Features
 
-- Add verbose feature to `favorite_query` command. (Thanks: [Zhaolong Zhu])
-  - `\f query` does not show the full SQL.
-  - `\f+ query` shows the full SQL.
-- Add prompt format of file's basename. (Thanks: [elig0n])
+* Add verbose feature to `favorite_query` command. (Thanks: [Zhaolong Zhu])
+  * `\f query` does not show the full SQL.
+  * `\f+ query` shows the full SQL.
+* Add prompt format of file's basename. (Thanks: [elig0n])
 
 ### Bug Fixes
 
-- Fix compatibility with sqlparse >= 0.4.0. (Thanks: [chocolateboy])
-- Fix invalid utf-8 exception. (Thanks: [Amjith])
+* Fix compatibility with sqlparse >= 0.4.0. (Thanks: [chocolateboy])
+* Fix invalid utf-8 exception. (Thanks: [Amjith])
 
 ## 1.4.1 - 2020-07-27
 
 ### Bug Fixes
 
-- Fix setup.py to set `long_description_content_type` as markdown.
+* Fix setup.py to set `long_description_content_type` as markdown.
 
 ## 1.4.0 - 2020-07-27
 
 ### Features
 
-- Add NULLS FIRST and NULLS LAST to keywords. (Thanks: [Amjith])
+* Add NULLS FIRST and NULLS LAST to keywords. (Thanks: [Amjith])
 
 ## 1.3.2 - 2020-03-11
 
-- Fix the completion engine to work with newer sqlparse.
+* Fix the completion engine to work with newer sqlparse.
 
 ## 1.3.1 - 2020-03-11
 
-- Remove the version pinning of sqlparse package.
+* Remove the version pinning of sqlparse package.
 
 ## 1.3.0 - 2020-02-11
 
 ### Features
 
-- Added `.import` command for importing data from file into table. (Thanks: [Zhaolong Zhu])
-- Upgraded to prompt-toolkit 3.x.
+* Added `.import` command for importing data from file into table. (Thanks: [Zhaolong Zhu])
+* Upgraded to prompt-toolkit 3.x.
 
 ## 1.2.0 - 2019-10-26
 
 ### Features
 
-- Enhance the `describe` command. (Thanks: [Amjith])
-- Autocomplete table names for special commands. (Thanks: [Amjith])
+* Enhance the `describe` command. (Thanks: [Amjith])
+* Autocomplete table names for special commands. (Thanks: [Amjith])
 
 ## 1.1.0 - 2019-07-14
 
 ### Features
 
-- Added `.read` command for reading scripts.
-- Added `.load` command for loading extension libraries. (Thanks: [Zhiming Wang])
-- Add support for using `?` as a placeholder in the favorite queries. (Thanks: [Amjith])
-- Added shift-tab to select the previous entry in the completion menu. [Amjith]
-- Added `describe` and `desc` keywords.
+* Added `.read` command for reading scripts.
+* Added `.load` command for loading extension libraries. (Thanks: [Zhiming Wang])
+* Add support for using `?` as a placeholder in the favorite queries. (Thanks: [Amjith])
+* Added shift-tab to select the previous entry in the completion menu. [Amjith]
+* Added `describe` and `desc` keywords.
 
 ### Bug Fixes
 
-- Clear error message when directory does not exist. (Thanks: [Irina Truong])
+* Clear error message when directory does not exist. (Thanks: [Irina Truong])
 
 ## 1.0.0 - 2019-01-04
 
-- To new beginnings. :tada:
+* To new beginnings. :tada:
 
 [Amjith]: https://blog.amjith.com
 [chocolateboy]: https://github.com/chocolateboy
 [Irina Truong]: https://github.com/j-bennet
-[Shawn Chapla]: https://github.com/shwnchpl
 [Zhaolong Zhu]: https://github.com/zzl0
 [Zhiming Wang]: https://github.com/zmwangx
-[Bjørnar Smestad]: https://brendesmestad.no
diff --git a/litecli/packages/special/dbcommands.py b/litecli/packages/special/dbcommands.py
index c0ed7e4..d779764 100644
--- a/litecli/packages/special/dbcommands.py
+++ b/litecli/packages/special/dbcommands.py
@@ -215,10 +215,9 @@ def load_extension(cur, arg, **_):
 )
 def describe(cur, arg, **_):
     if arg:
-        args = (arg,)
         query = """
             PRAGMA table_info({})
-        """.format(args)
+        """.format(arg)
     else:
         return list_tables(cur)
 


Test patch:
diff --git a/tests/test_dbspecial.py b/tests/test_dbspecial.py
index d3c7c07..5c43fac 100644
--- a/tests/test_dbspecial.py
+++ b/tests/test_dbspecial.py
@@ -2,6 +2,7 @@ from litecli.packages.completion_engine import suggest_type
 from test_completion_engine import sorted_dicts
 from litecli.packages.special.utils import format_uptime
 from litecli.packages.special.utils import check_if_sqlitedotcommand
+from utils import run, dbtest, assert_result_equal
 
 
 def test_import_first_argument():
@@ -88,3 +89,21 @@ def test_check_if_sqlitedotcommand():
     ]
     for command, expected_result in test_cases:
         assert check_if_sqlitedotcommand(command) == expected_result
+
+
+@dbtest
+def test_special_d(executor):
+    run(executor, """create table tst_tbl1(a text)""")
+    results = run(executor, """\\d""")
+
+    assert_result_equal(results, headers=["name"], rows=[("tst_tbl1",)], status="")
+
+
+@dbtest
+def test_special_d_w_arg(executor):
+    run(executor, """create table tst_tbl1(a text)""")
+    results = run(executor, """\\d tst_tbl1""")
+
+    assert_result_equal(
+        results, headers=["cid", "name", "type", "notnull", "dflt_value", "pk"], rows=[(0, "a", "TEXT", 0, None, 0)], status=""
+    )
diff --git a/tests/test_sqlexecute.py b/tests/test_sqlexecute.py
index 0181b2c..2bdc84c 100644
--- a/tests/test_sqlexecute.py
+++ b/tests/test_sqlexecute.py
@@ -4,34 +4,10 @@ import os
 
 import pytest
 
-from utils import run, dbtest, set_expanded_output, is_expanded_output
+from utils import run, dbtest, set_expanded_output, is_expanded_output, assert_result_equal
 from sqlite3 import OperationalError, ProgrammingError
 
 
-def assert_result_equal(
-    result,
-    title=None,
-    rows=None,
-    headers=None,
-    status=None,
-    auto_status=True,
-    assert_contains=False,
-):
-    """Assert that an sqlexecute.run() result matches the expected values."""
-    if status is None and auto_status and rows:
-        status = "{} row{} in set".format(len(rows), "s" if len(rows) > 1 else "")
-    fields = {"title": title, "rows": rows, "headers": headers, "status": status}
-
-    if assert_contains:
-        # Do a loose match on the results using the *in* operator.
-        for key, field in fields.items():
-            if field:
-                assert field in result[0][key]
-    else:
-        # Do an exact match on the fields.
-        assert result == [fields]
-
-
 @dbtest
 def test_conn(executor):
     run(executor, """create table test(a text)""")
diff --git a/tests/utils.py b/tests/utils.py
index 4bacc94..79d59e6 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -88,3 +88,27 @@ def send_ctrl_c(wait_seconds):
     ctrl_c_process = multiprocessing.Process(target=send_ctrl_c_to_pid, args=(os.getpid(), wait_seconds))
     ctrl_c_process.start()
     return ctrl_c_process
+
+
+def assert_result_equal(
+    result,
+    title=None,
+    rows=None,
+    headers=None,
+    status=None,
+    auto_status=True,
+    assert_contains=False,
+):
+    """Assert that an sqlexecute.run() result matches the expected values."""
+    if status is None and auto_status and rows:
+        status = "{} row{} in set".format(len(rows), "s" if len(rows) > 1 else "")
+    fields = {"title": title, "rows": rows, "headers": headers, "status": status}
+
+    if assert_contains:
+        # Do a loose match on the results using the *in* operator.
+        for key, field in fields.items():
+            if field:
+                assert field in result[0][key]
+    else:
+        # Do an exact match on the fields.
+        assert result == [fields]