We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
docs: explain you only need to close a conn.raw_sql() if you are running a query that returns results
### Please describe the issue

I was following the advise of https://ibis-project.org/how-to/extending/sql#backend.raw_sql, and closing the returned cursor.

But the actual SQL I was running was an `EXPLAIN SELECT ...` query. I was getting errors, eventually after a lot of headscratching I think I have found that I do NOT want to close the cursor after this query.

Possible actions:
1. be more precise with what sorts of SQL statements require the close.
2. make it so ALL queries require a close, so there is no distinction.
3. UX: make the returned object itself closable, so I don't have to do `from contextlib import closing` and I can just do `with conn.raw_sql(x) as cursor:`

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ibis/config.py`
Content:
```
1 from __future__ import annotations
2 
3 import contextlib
4 from typing import Annotated, Any, Callable, Optional
5 
6 from public import public
7 
8 import ibis.common.exceptions as com
9 from ibis.common.grounds import Annotable
10 from ibis.common.patterns import Between
11 
12 PosInt = Annotated[int, Between(lower=0)]
13 
14 
15 class Config(Annotable):
16     def get(self, key: str) -> Any:
17         value = self
18         for field in key.split("."):
19             value = getattr(value, field)
20         return value
21 
22     def set(self, key: str, value: Any) -> None:
23         *prefix, key = key.split(".")
24         conf = self
25         for field in prefix:
26             conf = getattr(conf, field)
27         setattr(conf, key, value)
28 
29     @contextlib.contextmanager
30     def _with_temporary(self, options):
31         try:
32             old = {}
33             for key, value in options.items():
34                 old[key] = self.get(key)
35                 self.set(key, value)
36             yield
37         finally:
38             for key, value in old.items():
39                 self.set(key, value)
40 
41     def __call__(self, options):
42         return self._with_temporary(options)
43 
44 
45 class ContextAdjustment(Config):
46     """Options related to time context adjustment.
47 
48     Attributes
49     ----------
50     time_col : str
51         Name of the timestamp column for execution with a `timecontext`. See
52         `ibis/expr/timecontext.py` for details.
53 
54     """
55 
56     time_col: str = "time"
57 
58 
59 class SQL(Config):
60     """SQL-related options.
61 
62     Attributes
63     ----------
64     default_limit : int | None
65         Number of rows to be retrieved for a table expression without an
66         explicit limit. [](`None`) means no limit.
67     default_dialect : str
68         Dialect to use for printing SQL when the backend cannot be determined.
69 
70     """
71 
72     default_limit: Optional[PosInt] = None
73     default_dialect: str = "duckdb"
74 
75 
76 class Interactive(Config):
77     """Options controlling the interactive repr.
78 
79     Attributes
80     ----------
81     max_rows : int
82         Maximum rows to pretty print.
83     max_columns : int | None
84         The maximum number of columns to pretty print. If 0 (the default), the
85         number of columns will be inferred from output console size. Set to
86         `None` for no limit.
87     max_length : int
88         Maximum length for pretty-printed arrays and maps.
89     max_string : int
90         Maximum length for pretty-printed strings.
91     max_depth : int
92         Maximum depth for nested data types.
93     show_types : bool
94         Show the inferred type of value expressions in the interactive repr.
95 
96     """
97 
98     max_rows: int = 10
99     max_columns: Optional[int] = 0
100     max_length: int = 2
101     max_string: int = 80
102     max_depth: int = 1
103     show_types: bool = True
104 
105 
106 class Repr(Config):
107     """Expression printing options.
108 
109     Attributes
110     ----------
111     depth : int
112         The maximum number of expression nodes to print when repring.
113     table_columns : int
114         The number of columns to show in leaf table expressions.
115     table_rows : int
116         The number of rows to show for in memory tables.
117     query_text_length : int
118         The maximum number of characters to show in the `query` field repr of
119         SQLQueryResult operations.
120     show_types : bool
121         Show the inferred type of value expressions in the repr.
122     interactive : bool
123         Options controlling the interactive repr.
124 
125     """
126 
127     depth: Optional[PosInt] = None
128     table_columns: Optional[PosInt] = None
129     table_rows: PosInt = 10
130     query_text_length: PosInt = 80
131     show_types: bool = False
132     interactive: Interactive = Interactive()
133 
134 
135 class Options(Config):
136     """Ibis configuration options.
137 
138     Attributes
139     ----------
140     interactive : bool
141         Show the first few rows of computing an expression when in a repl.
142     repr : Repr
143         Options controlling expression printing.
144     verbose : bool
145         Run in verbose mode if [](`True`)
146     verbose_log: Callable[[str], None] | None
147         A callable to use when logging.
148     graphviz_repr : bool
149         Render expressions as GraphViz PNGs when running in a Jupyter notebook.
150     default_backend : Optional[ibis.backends.BaseBackend], default None
151         The default backend to use for execution, defaults to DuckDB if not
152         set.
153     context_adjustment : ContextAdjustment
154         Options related to time context adjustment.
155     sql: SQL
156         SQL-related options.
157     clickhouse : Config | None
158         Clickhouse specific options.
159     dask : Config | None
160         Dask specific options.
161     impala : Config | None
162         Impala specific options.
163     pandas : Config | None
164         Pandas specific options.
165     pyspark : Config | None
166         PySpark specific options.
167 
168     """
169 
170     interactive: bool = False
171     repr: Repr = Repr()
172     verbose: bool = False
173     verbose_log: Optional[Callable] = None
174     graphviz_repr: bool = False
175     default_backend: Optional[Any] = None
176     context_adjustment: ContextAdjustment = ContextAdjustment()
177     sql: SQL = SQL()
178     clickhouse: Optional[Config] = None
179     dask: Optional[Config] = None
180     impala: Optional[Config] = None
181     pandas: Optional[Config] = None
182     pyspark: Optional[Config] = None
183 
184 
185 def _default_backend() -> Any:
186     if (backend := options.default_backend) is not None:
187         return backend
188 
189     try:
190         import duckdb as _  # noqa: F401
191     except ImportError:
192         raise com.IbisError(
193             """\
194 You have used a function that relies on the default backend, but the default
195 backend (DuckDB) is not installed.
196 
197 You may specify an alternate backend to use, e.g.
198 
199 ibis.set_backend("polars")
200 
201 or to install the DuckDB backend, run:
202 
203     pip install 'ibis-framework[duckdb]'
204 
205 or
206 
207     conda install -c conda-forge ibis-framework
208 
209 For more information on available backends, visit https://ibis-project.org/install
210 """
211         )
212 
213     import ibis
214 
215     options.default_backend = con = ibis.duckdb.connect(":memory:")
216     return con
217 
218 
219 options = Options()
220 
221 
222 @public
223 def option_context(key, new_value):
224     return options({key: new_value})
225 
226 
227 public(options=options)
228
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/ibis/config.py b/ibis/config.py
--- a/ibis/config.py
+++ b/ibis/config.py
@@ -147,7 +147,7 @@
         A callable to use when logging.
     graphviz_repr : bool
         Render expressions as GraphViz PNGs when running in a Jupyter notebook.
-    default_backend : Optional[ibis.backends.BaseBackend], default None
+    default_backend : Optional[ibis.backends.BaseBackend]
         The default backend to use for execution, defaults to DuckDB if not
         set.
     context_adjustment : ContextAdjustment

{"golden_diff": "diff --git a/ibis/config.py b/ibis/config.py\n--- a/ibis/config.py\n+++ b/ibis/config.py\n@@ -147,7 +147,7 @@\n         A callable to use when logging.\n     graphviz_repr : bool\n         Render expressions as GraphViz PNGs when running in a Jupyter notebook.\n-    default_backend : Optional[ibis.backends.BaseBackend], default None\n+    default_backend : Optional[ibis.backends.BaseBackend]\n         The default backend to use for execution, defaults to DuckDB if not\n         set.\n     context_adjustment : ContextAdjustment\n", "issue": "docs: explain you only need to close a conn.raw_sql() if you are running a query that returns results\n### Please describe the issue\n\nI was following the advise of https://ibis-project.org/how-to/extending/sql#backend.raw_sql, and closing the returned cursor.\r\n\r\nBut the actual SQL I was running was an `EXPLAIN SELECT ...` query. I was getting errors, eventually after a lot of headscratching I think I have found that I do NOT want to close the cursor after this query.\r\n\r\nPossible actions:\r\n1. be more precise with what sorts of SQL statements require the close.\r\n2. make it so ALL queries require a close, so there is no distinction.\r\n3. UX: make the returned object itself closable, so I don't have to do `from contextlib import closing` and I can just do `with conn.raw_sql(x) as cursor:`\n\n### Code of Conduct\n\n- [X] I agree to follow this project's Code of Conduct\n", "before_files": [{"content": "from __future__ import annotations\n\nimport contextlib\nfrom typing import Annotated, Any, Callable, Optional\n\nfrom public import public\n\nimport ibis.common.exceptions as com\nfrom ibis.common.grounds import Annotable\nfrom ibis.common.patterns import Between\n\nPosInt = Annotated[int, Between(lower=0)]\n\n\nclass Config(Annotable):\n    def get(self, key: str) -> Any:\n        value = self\n        for field in key.split(\".\"):\n            value = getattr(value, field)\n        return value\n\n    def set(self, key: str, value: Any) -> None:\n        *prefix, key = key.split(\".\")\n        conf = self\n        for field in prefix:\n            conf = getattr(conf, field)\n        setattr(conf, key, value)\n\n    @contextlib.contextmanager\n    def _with_temporary(self, options):\n        try:\n            old = {}\n            for key, value in options.items():\n                old[key] = self.get(key)\n                self.set(key, value)\n            yield\n        finally:\n            for key, value in old.items():\n                self.set(key, value)\n\n    def __call__(self, options):\n        return self._with_temporary(options)\n\n\nclass ContextAdjustment(Config):\n    \"\"\"Options related to time context adjustment.\n\n    Attributes\n    ----------\n    time_col : str\n        Name of the timestamp column for execution with a `timecontext`. See\n        `ibis/expr/timecontext.py` for details.\n\n    \"\"\"\n\n    time_col: str = \"time\"\n\n\nclass SQL(Config):\n    \"\"\"SQL-related options.\n\n    Attributes\n    ----------\n    default_limit : int | None\n        Number of rows to be retrieved for a table expression without an\n        explicit limit. [](`None`) means no limit.\n    default_dialect : str\n        Dialect to use for printing SQL when the backend cannot be determined.\n\n    \"\"\"\n\n    default_limit: Optional[PosInt] = None\n    default_dialect: str = \"duckdb\"\n\n\nclass Interactive(Config):\n    \"\"\"Options controlling the interactive repr.\n\n    Attributes\n    ----------\n    max_rows : int\n        Maximum rows to pretty print.\n    max_columns : int | None\n        The maximum number of columns to pretty print. If 0 (the default), the\n        number of columns will be inferred from output console size. Set to\n        `None` for no limit.\n    max_length : int\n        Maximum length for pretty-printed arrays and maps.\n    max_string : int\n        Maximum length for pretty-printed strings.\n    max_depth : int\n        Maximum depth for nested data types.\n    show_types : bool\n        Show the inferred type of value expressions in the interactive repr.\n\n    \"\"\"\n\n    max_rows: int = 10\n    max_columns: Optional[int] = 0\n    max_length: int = 2\n    max_string: int = 80\n    max_depth: int = 1\n    show_types: bool = True\n\n\nclass Repr(Config):\n    \"\"\"Expression printing options.\n\n    Attributes\n    ----------\n    depth : int\n        The maximum number of expression nodes to print when repring.\n    table_columns : int\n        The number of columns to show in leaf table expressions.\n    table_rows : int\n        The number of rows to show for in memory tables.\n    query_text_length : int\n        The maximum number of characters to show in the `query` field repr of\n        SQLQueryResult operations.\n    show_types : bool\n        Show the inferred type of value expressions in the repr.\n    interactive : bool\n        Options controlling the interactive repr.\n\n    \"\"\"\n\n    depth: Optional[PosInt] = None\n    table_columns: Optional[PosInt] = None\n    table_rows: PosInt = 10\n    query_text_length: PosInt = 80\n    show_types: bool = False\n    interactive: Interactive = Interactive()\n\n\nclass Options(Config):\n    \"\"\"Ibis configuration options.\n\n    Attributes\n    ----------\n    interactive : bool\n        Show the first few rows of computing an expression when in a repl.\n    repr : Repr\n        Options controlling expression printing.\n    verbose : bool\n        Run in verbose mode if [](`True`)\n    verbose_log: Callable[[str], None] | None\n        A callable to use when logging.\n    graphviz_repr : bool\n        Render expressions as GraphViz PNGs when running in a Jupyter notebook.\n    default_backend : Optional[ibis.backends.BaseBackend], default None\n        The default backend to use for execution, defaults to DuckDB if not\n        set.\n    context_adjustment : ContextAdjustment\n        Options related to time context adjustment.\n    sql: SQL\n        SQL-related options.\n    clickhouse : Config | None\n        Clickhouse specific options.\n    dask : Config | None\n        Dask specific options.\n    impala : Config | None\n        Impala specific options.\n    pandas : Config | None\n        Pandas specific options.\n    pyspark : Config | None\n        PySpark specific options.\n\n    \"\"\"\n\n    interactive: bool = False\n    repr: Repr = Repr()\n    verbose: bool = False\n    verbose_log: Optional[Callable] = None\n    graphviz_repr: bool = False\n    default_backend: Optional[Any] = None\n    context_adjustment: ContextAdjustment = ContextAdjustment()\n    sql: SQL = SQL()\n    clickhouse: Optional[Config] = None\n    dask: Optional[Config] = None\n    impala: Optional[Config] = None\n    pandas: Optional[Config] = None\n    pyspark: Optional[Config] = None\n\n\ndef _default_backend() -> Any:\n    if (backend := options.default_backend) is not None:\n        return backend\n\n    try:\n        import duckdb as _  # noqa: F401\n    except ImportError:\n        raise com.IbisError(\n            \"\"\"\\\nYou have used a function that relies on the default backend, but the default\nbackend (DuckDB) is not installed.\n\nYou may specify an alternate backend to use, e.g.\n\nibis.set_backend(\"polars\")\n\nor to install the DuckDB backend, run:\n\n    pip install 'ibis-framework[duckdb]'\n\nor\n\n    conda install -c conda-forge ibis-framework\n\nFor more information on available backends, visit https://ibis-project.org/install\n\"\"\"\n        )\n\n    import ibis\n\n    options.default_backend = con = ibis.duckdb.connect(\":memory:\")\n    return con\n\n\noptions = Options()\n\n\n@public\ndef option_context(key, new_value):\n    return options({key: new_value})\n\n\npublic(options=options)\n", "path": "ibis/config.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport contextlib\nfrom typing import Annotated, Any, Callable, Optional\n\nfrom public import public\n\nimport ibis.common.exceptions as com\nfrom ibis.common.grounds import Annotable\nfrom ibis.common.patterns import Between\n\nPosInt = Annotated[int, Between(lower=0)]\n\n\nclass Config(Annotable):\n    def get(self, key: str) -> Any:\n        value = self\n        for field in key.split(\".\"):\n            value = getattr(value, field)\n        return value\n\n    def set(self, key: str, value: Any) -> None:\n        *prefix, key = key.split(\".\")\n        conf = self\n        for field in prefix:\n            conf = getattr(conf, field)\n        setattr(conf, key, value)\n\n    @contextlib.contextmanager\n    def _with_temporary(self, options):\n        try:\n            old = {}\n            for key, value in options.items():\n                old[key] = self.get(key)\n                self.set(key, value)\n            yield\n        finally:\n            for key, value in old.items():\n                self.set(key, value)\n\n    def __call__(self, options):\n        return self._with_temporary(options)\n\n\nclass ContextAdjustment(Config):\n    \"\"\"Options related to time context adjustment.\n\n    Attributes\n    ----------\n    time_col : str\n        Name of the timestamp column for execution with a `timecontext`. See\n        `ibis/expr/timecontext.py` for details.\n\n    \"\"\"\n\n    time_col: str = \"time\"\n\n\nclass SQL(Config):\n    \"\"\"SQL-related options.\n\n    Attributes\n    ----------\n    default_limit : int | None\n        Number of rows to be retrieved for a table expression without an\n        explicit limit. [](`None`) means no limit.\n    default_dialect : str\n        Dialect to use for printing SQL when the backend cannot be determined.\n\n    \"\"\"\n\n    default_limit: Optional[PosInt] = None\n    default_dialect: str = \"duckdb\"\n\n\nclass Interactive(Config):\n    \"\"\"Options controlling the interactive repr.\n\n    Attributes\n    ----------\n    max_rows : int\n        Maximum rows to pretty print.\n    max_columns : int | None\n        The maximum number of columns to pretty print. If 0 (the default), the\n        number of columns will be inferred from output console size. Set to\n        `None` for no limit.\n    max_length : int\n        Maximum length for pretty-printed arrays and maps.\n    max_string : int\n        Maximum length for pretty-printed strings.\n    max_depth : int\n        Maximum depth for nested data types.\n    show_types : bool\n        Show the inferred type of value expressions in the interactive repr.\n\n    \"\"\"\n\n    max_rows: int = 10\n    max_columns: Optional[int] = 0\n    max_length: int = 2\n    max_string: int = 80\n    max_depth: int = 1\n    show_types: bool = True\n\n\nclass Repr(Config):\n    \"\"\"Expression printing options.\n\n    Attributes\n    ----------\n    depth : int\n        The maximum number of expression nodes to print when repring.\n    table_columns : int\n        The number of columns to show in leaf table expressions.\n    table_rows : int\n        The number of rows to show for in memory tables.\n    query_text_length : int\n        The maximum number of characters to show in the `query` field repr of\n        SQLQueryResult operations.\n    show_types : bool\n        Show the inferred type of value expressions in the repr.\n    interactive : bool\n        Options controlling the interactive repr.\n\n    \"\"\"\n\n    depth: Optional[PosInt] = None\n    table_columns: Optional[PosInt] = None\n    table_rows: PosInt = 10\n    query_text_length: PosInt = 80\n    show_types: bool = False\n    interactive: Interactive = Interactive()\n\n\nclass Options(Config):\n    \"\"\"Ibis configuration options.\n\n    Attributes\n    ----------\n    interactive : bool\n        Show the first few rows of computing an expression when in a repl.\n    repr : Repr\n        Options controlling expression printing.\n    verbose : bool\n        Run in verbose mode if [](`True`)\n    verbose_log: Callable[[str], None] | None\n        A callable to use when logging.\n    graphviz_repr : bool\n        Render expressions as GraphViz PNGs when running in a Jupyter notebook.\n    default_backend : Optional[ibis.backends.BaseBackend]\n        The default backend to use for execution, defaults to DuckDB if not\n        set.\n    context_adjustment : ContextAdjustment\n        Options related to time context adjustment.\n    sql: SQL\n        SQL-related options.\n    clickhouse : Config | None\n        Clickhouse specific options.\n    dask : Config | None\n        Dask specific options.\n    impala : Config | None\n        Impala specific options.\n    pandas : Config | None\n        Pandas specific options.\n    pyspark : Config | None\n        PySpark specific options.\n\n    \"\"\"\n\n    interactive: bool = False\n    repr: Repr = Repr()\n    verbose: bool = False\n    verbose_log: Optional[Callable] = None\n    graphviz_repr: bool = False\n    default_backend: Optional[Any] = None\n    context_adjustment: ContextAdjustment = ContextAdjustment()\n    sql: SQL = SQL()\n    clickhouse: Optional[Config] = None\n    dask: Optional[Config] = None\n    impala: Optional[Config] = None\n    pandas: Optional[Config] = None\n    pyspark: Optional[Config] = None\n\n\ndef _default_backend() -> Any:\n    if (backend := options.default_backend) is not None:\n        return backend\n\n    try:\n        import duckdb as _  # noqa: F401\n    except ImportError:\n        raise com.IbisError(\n            \"\"\"\\\nYou have used a function that relies on the default backend, but the default\nbackend (DuckDB) is not installed.\n\nYou may specify an alternate backend to use, e.g.\n\nibis.set_backend(\"polars\")\n\nor to install the DuckDB backend, run:\n\n    pip install 'ibis-framework[duckdb]'\n\nor\n\n    conda install -c conda-forge ibis-framework\n\nFor more information on available backends, visit https://ibis-project.org/install\n\"\"\"\n        )\n\n    import ibis\n\n    options.default_backend = con = ibis.duckdb.connect(\":memory:\")\n    return con\n\n\noptions = Options()\n\n\n@public\ndef option_context(key, new_value):\n    return options({key: new_value})\n\n\npublic(options=options)\n", "path": "ibis/config.py"}]}