We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
feat: support amending/overriding reflected types in SQLite backend
I ran into a few problems trying to load data from [PyPI-Data](https://github.com/sethmlarson/pypi-data) that I'm not sure how best to solve:

- The database schemas use `STRING` instead of `TEXT` for string types (this has since been fixed, but [doesn't seem to be part of the latest release](https://github.com/sethmlarson/pypi-data/issues/12)). It would be nice if we could just force sqlalchemy to treat these columns as `TEXT` types rather than falling back to `NUMERIC`. The SQLAlchemy docs illustrate one way of [overriding reflected types](https://docs.sqlalchemy.org/en/14/core/reflection.html#overriding-reflected-columns), but like most things in SQLAlchemy there are probably other ways of handling this.
- The timestamp columns are using RFC3339 format (`YYYY-mm-ddTHH:MM:SS`) which isn't handled by SQLAlchemy's standard datetime parsing routine (it doesn't like the `T` delimiter). The way around this in SQLAlchemy is to pass [additional options to the DATETIME type constructor](https://docs.sqlalchemy.org/en/14/dialects/sqlite.html#sqlalchemy.dialects.sqlite.DATETIME), but ibis currently AFAICT doesn't expose this functionality.

I'm honestly not sure what a good API here would be.

Brainstorming a bit, perhaps additional arguments to the `ibis.sqlite.connect` providing:

- A type mapping from SQLITE type (e.g. `TIMESTAMP`) to ibis type (`typemap={...}`?). This would let us map `{"STRING": ibis.expr.datatypes.String()}`
- A datetime format string in strftime syntax to use when parsing `TIMESTAMP` columns to datetime objects (`timestamp_format=...`?). This assumes the database uses the same datetime format for all tables, but hopefully that's the case.

A few things to like about this approach:

- It doesn't expose that SQLAlchemy is used to the user anywhere. Everything is a mapping between SQLite concepts and ibis itself. This would give us the freedom to drop SQLAlchemy if we wanted without forcing an API change.
- It doesn't require listing the full table schemas for all poorly typed columns - the user only needs to pass in overrides for the specific SQLite type names that require it.

For my use case the connect call might look like:

```python
con = ibis.sqlite.connect("pypi.db", typemap={"STRING": ibis.expr.datatypes.String()}, timestamp_format="%Y-%m-%dT%H:%M:%S")
```

There's probably a better way of handling this though.

--- 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/backends/sqlite/__init__.py`
Content:
```
1 # Copyright 2015 Cloudera Inc.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 from __future__ import annotations
16 
17 import datetime
18 import sqlite3
19 import warnings
20 from pathlib import Path
21 from typing import TYPE_CHECKING
22 
23 import pandas as pd
24 import sqlalchemy as sa
25 from sqlalchemy.dialects.sqlite import DATETIME, TIMESTAMP
26 
27 if TYPE_CHECKING:
28     import ibis.expr.types as ir
29 
30 from ibis.backends.base import Database
31 from ibis.backends.base.sql.alchemy import BaseAlchemyBackend
32 from ibis.backends.sqlite import udf
33 from ibis.backends.sqlite.compiler import SQLiteCompiler
34 
35 
36 def to_datetime(value: str | None) -> datetime.datetime | None:
37     """Convert a str to a datetime according to SQLite's rules, ignoring `None`
38     values"""
39     if value is None:
40         return None
41     if value.endswith("Z"):
42         # Parse and set the timezone as UTC
43         o = datetime.datetime.fromisoformat(value[:-1]).replace(
44             tzinfo=datetime.timezone.utc
45         )
46     else:
47         o = datetime.datetime.fromisoformat(value)
48         if o.tzinfo:
49             # Convert any aware datetime to UTC
50             return o.astimezone(datetime.timezone.utc)
51     return o
52 
53 
54 class ISODATETIME(DATETIME):
55     """A thin datetime type to override sqlalchemy's datetime parsing to
56     support a wider range of timestamp formats accepted by SQLite.
57 
58     See https://sqlite.org/lang_datefunc.html#time_values for the full
59     list of datetime formats SQLite accepts.
60     """
61 
62     def result_processor(self, value, dialect):
63         return to_datetime
64 
65 
66 class Backend(BaseAlchemyBackend):
67     name = 'sqlite'
68     # TODO check if there is a reason to not use the parent AlchemyDatabase, or
69     # if there is technical debt that makes this required
70     database_class = Database
71     compiler = SQLiteCompiler
72 
73     def __getstate__(self) -> dict:
74         r = super().__getstate__()
75         r.update(
76             dict(
77                 compiler=self.compiler,
78                 database_name=self.database_name,
79                 _con=None,  # clear connection on copy()
80                 _meta=None,
81             )
82         )
83         return r
84 
85     def do_connect(
86         self,
87         database: str | Path | None = None,
88         path: str | Path | None = None,
89     ) -> None:
90         """Create an Ibis client connected to a SQLite database.
91 
92         Multiple database files can be accessed using the `attach()` method.
93 
94         Parameters
95         ----------
96         database
97             File path to the SQLite database file. If `None`, creates an
98             in-memory transient database and you can use attach() to add more
99             files
100 
101         Examples
102         --------
103         >>> import ibis
104         >>> ibis.sqlite.connect("path/to/my/sqlite.db")
105         """
106         if path is not None:
107             warnings.warn(
108                 "The `path` argument is deprecated in 4.0. Use `database=...`"
109             )
110             database = path
111 
112         self.database_name = "main"
113 
114         engine = sa.create_engine(
115             f"sqlite:///{database if database is not None else ':memory:'}"
116         )
117 
118         sqlite3.register_adapter(pd.Timestamp, lambda value: value.isoformat())
119 
120         @sa.event.listens_for(engine, "connect")
121         def connect(dbapi_connection, connection_record):
122             """Register UDFs on connection."""
123             udf.register_all(dbapi_connection)
124 
125         super().do_connect(engine)
126 
127         @sa.event.listens_for(self.meta, "column_reflect")
128         def column_reflect(inspector, table, column_info):
129             if type(column_info["type"]) is TIMESTAMP:
130                 column_info["type"] = ISODATETIME()
131 
132     def attach(
133         self,
134         name: str,
135         path: str | Path,
136     ) -> None:
137         """Connect another SQLite database file to the current connection.
138 
139         Parameters
140         ----------
141         name
142             Database name within SQLite
143         path
144             Path to sqlite3 database file
145         """
146         quoted_name = self.con.dialect.identifier_preparer.quote(name)
147         self.raw_sql(f"ATTACH DATABASE {path!r} AS {quoted_name}")
148 
149     def _get_sqla_table(self, name, schema=None, autoload=True):
150         return sa.Table(
151             name,
152             self.meta,
153             schema=schema or self.current_database,
154             autoload=autoload,
155         )
156 
157     def table(self, name: str, database: str | None = None) -> ir.Table:
158         """Create a table expression from a table in the SQLite database.
159 
160         Parameters
161         ----------
162         name
163             Table name
164         database
165             Name of the attached database that the table is located in.
166 
167         Returns
168         -------
169         Table
170             Table expression
171         """
172         alch_table = self._get_sqla_table(name, schema=database)
173         node = self.table_class(source=self, sqla_table=alch_table)
174         return self.table_expr_class(node)
175 
176     def _table_from_schema(
177         self, name, schema, database: str | None = None
178     ) -> sa.Table:
179         columns = self._columns_from_schema(name, schema)
180         return sa.Table(name, self.meta, schema=database, *columns)
181 
182     @property
183     def _current_schema(self) -> str | None:
184         return self.current_database
185
```
--- 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/backends/sqlite/__init__.py b/ibis/backends/sqlite/__init__.py
--- a/ibis/backends/sqlite/__init__.py
+++ b/ibis/backends/sqlite/__init__.py
@@ -26,11 +26,13 @@
 
 if TYPE_CHECKING:
     import ibis.expr.types as ir
+    import ibis.expr.datatypes as dt
 
 from ibis.backends.base import Database
-from ibis.backends.base.sql.alchemy import BaseAlchemyBackend
+from ibis.backends.base.sql.alchemy import BaseAlchemyBackend, to_sqla_type
 from ibis.backends.sqlite import udf
 from ibis.backends.sqlite.compiler import SQLiteCompiler
+from ibis.expr.schema import datatype
 
 
 def to_datetime(value: str | None) -> datetime.datetime | None:
@@ -86,6 +88,7 @@
         self,
         database: str | Path | None = None,
         path: str | Path | None = None,
+        type_map: dict[str, str | dt.DataType] | None = None,
     ) -> None:
         """Create an Ibis client connected to a SQLite database.
 
@@ -97,6 +100,10 @@
             File path to the SQLite database file. If `None`, creates an
             in-memory transient database and you can use attach() to add more
             files
+        type_map
+            An optional mapping from a string name of a SQLite "type" to the
+            corresponding ibis DataType that it represents. This can be used
+            to override schema inference for a given SQLite database.
 
         Examples
         --------
@@ -115,6 +122,24 @@
             f"sqlite:///{database if database is not None else ':memory:'}"
         )
 
+        if type_map:
+            # Patch out ischema_names for the instantiated dialect. This
+            # attribute is required for all SQLAlchemy dialects, but as no
+            # public way of modifying it for a given dialect. Patching seems
+            # easier than subclassing the builtin SQLite dialect, and achieves
+            # the same desired behavior.
+            def _to_ischema_val(t):
+                sa_type = to_sqla_type(datatype(t))
+                if isinstance(sa_type, sa.types.TypeEngine):
+                    # SQLAlchemy expects a callable here, rather than an
+                    # instance. Use a lambda to work around this.
+                    return lambda: sa_type
+                return sa_type
+
+            overrides = {k: _to_ischema_val(v) for k, v in type_map.items()}
+            engine.dialect.ischema_names = engine.dialect.ischema_names.copy()
+            engine.dialect.ischema_names.update(overrides)
+
         sqlite3.register_adapter(pd.Timestamp, lambda value: value.isoformat())
 
         @sa.event.listens_for(engine, "connect")

{"golden_diff": "diff --git a/ibis/backends/sqlite/__init__.py b/ibis/backends/sqlite/__init__.py\n--- a/ibis/backends/sqlite/__init__.py\n+++ b/ibis/backends/sqlite/__init__.py\n@@ -26,11 +26,13 @@\n \n if TYPE_CHECKING:\n     import ibis.expr.types as ir\n+    import ibis.expr.datatypes as dt\n \n from ibis.backends.base import Database\n-from ibis.backends.base.sql.alchemy import BaseAlchemyBackend\n+from ibis.backends.base.sql.alchemy import BaseAlchemyBackend, to_sqla_type\n from ibis.backends.sqlite import udf\n from ibis.backends.sqlite.compiler import SQLiteCompiler\n+from ibis.expr.schema import datatype\n \n \n def to_datetime(value: str | None) -> datetime.datetime | None:\n@@ -86,6 +88,7 @@\n         self,\n         database: str | Path | None = None,\n         path: str | Path | None = None,\n+        type_map: dict[str, str | dt.DataType] | None = None,\n     ) -> None:\n         \"\"\"Create an Ibis client connected to a SQLite database.\n \n@@ -97,6 +100,10 @@\n             File path to the SQLite database file. If `None`, creates an\n             in-memory transient database and you can use attach() to add more\n             files\n+        type_map\n+            An optional mapping from a string name of a SQLite \"type\" to the\n+            corresponding ibis DataType that it represents. This can be used\n+            to override schema inference for a given SQLite database.\n \n         Examples\n         --------\n@@ -115,6 +122,24 @@\n             f\"sqlite:///{database if database is not None else ':memory:'}\"\n         )\n \n+        if type_map:\n+            # Patch out ischema_names for the instantiated dialect. This\n+            # attribute is required for all SQLAlchemy dialects, but as no\n+            # public way of modifying it for a given dialect. Patching seems\n+            # easier than subclassing the builtin SQLite dialect, and achieves\n+            # the same desired behavior.\n+            def _to_ischema_val(t):\n+                sa_type = to_sqla_type(datatype(t))\n+                if isinstance(sa_type, sa.types.TypeEngine):\n+                    # SQLAlchemy expects a callable here, rather than an\n+                    # instance. Use a lambda to work around this.\n+                    return lambda: sa_type\n+                return sa_type\n+\n+            overrides = {k: _to_ischema_val(v) for k, v in type_map.items()}\n+            engine.dialect.ischema_names = engine.dialect.ischema_names.copy()\n+            engine.dialect.ischema_names.update(overrides)\n+\n         sqlite3.register_adapter(pd.Timestamp, lambda value: value.isoformat())\n \n         @sa.event.listens_for(engine, \"connect\")\n", "issue": "feat: support amending/overriding reflected types in SQLite backend\nI ran into a few problems trying to load data from [PyPI-Data](https://github.com/sethmlarson/pypi-data) that I'm not sure how best to solve:\r\n\r\n- The database schemas use `STRING` instead of `TEXT` for string types (this has since been fixed, but [doesn't seem to be part of the latest release](https://github.com/sethmlarson/pypi-data/issues/12)). It would be nice if we could just force sqlalchemy to treat these columns as `TEXT` types rather than falling back to `NUMERIC`. The SQLAlchemy docs illustrate one way of [overriding reflected types](https://docs.sqlalchemy.org/en/14/core/reflection.html#overriding-reflected-columns), but like most things in SQLAlchemy there are probably other ways of handling this.\r\n- The timestamp columns are using RFC3339 format (`YYYY-mm-ddTHH:MM:SS`) which isn't handled by SQLAlchemy's standard datetime parsing routine (it doesn't like the `T` delimiter). The way around this in SQLAlchemy is to pass [additional options to the DATETIME type constructor](https://docs.sqlalchemy.org/en/14/dialects/sqlite.html#sqlalchemy.dialects.sqlite.DATETIME), but ibis currently AFAICT doesn't expose this functionality.\r\n\r\nI'm honestly not sure what a good API here would be.\r\n\r\nBrainstorming a bit, perhaps additional arguments to the `ibis.sqlite.connect` providing:\r\n\r\n- A type mapping from SQLITE type (e.g. `TIMESTAMP`) to ibis type (`typemap={...}`?). This would let us map `{\"STRING\": ibis.expr.datatypes.String()}`\r\n- A datetime format string in strftime syntax to use when parsing `TIMESTAMP` columns to datetime objects (`timestamp_format=...`?). This assumes the database uses the same datetime format for all tables, but hopefully that's the case.\r\n\r\nA few things to like about this approach:\r\n\r\n- It doesn't expose that SQLAlchemy is used to the user anywhere. Everything is a mapping between SQLite concepts and ibis itself. This would give us the freedom to drop SQLAlchemy if we wanted without forcing an API change.\r\n- It doesn't require listing the full table schemas for all poorly typed columns - the user only needs to pass in overrides for the specific SQLite type names that require it.\r\n\r\nFor my use case the connect call might look like:\r\n\r\n```python\r\ncon = ibis.sqlite.connect(\"pypi.db\", typemap={\"STRING\": ibis.expr.datatypes.String()}, timestamp_format=\"%Y-%m-%dT%H:%M:%S\")\r\n```\r\n\r\nThere's probably a better way of handling this though.\n", "before_files": [{"content": "# Copyright 2015 Cloudera Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom __future__ import annotations\n\nimport datetime\nimport sqlite3\nimport warnings\nfrom pathlib import Path\nfrom typing import TYPE_CHECKING\n\nimport pandas as pd\nimport sqlalchemy as sa\nfrom sqlalchemy.dialects.sqlite import DATETIME, TIMESTAMP\n\nif TYPE_CHECKING:\n    import ibis.expr.types as ir\n\nfrom ibis.backends.base import Database\nfrom ibis.backends.base.sql.alchemy import BaseAlchemyBackend\nfrom ibis.backends.sqlite import udf\nfrom ibis.backends.sqlite.compiler import SQLiteCompiler\n\n\ndef to_datetime(value: str | None) -> datetime.datetime | None:\n    \"\"\"Convert a str to a datetime according to SQLite's rules, ignoring `None`\n    values\"\"\"\n    if value is None:\n        return None\n    if value.endswith(\"Z\"):\n        # Parse and set the timezone as UTC\n        o = datetime.datetime.fromisoformat(value[:-1]).replace(\n            tzinfo=datetime.timezone.utc\n        )\n    else:\n        o = datetime.datetime.fromisoformat(value)\n        if o.tzinfo:\n            # Convert any aware datetime to UTC\n            return o.astimezone(datetime.timezone.utc)\n    return o\n\n\nclass ISODATETIME(DATETIME):\n    \"\"\"A thin datetime type to override sqlalchemy's datetime parsing to\n    support a wider range of timestamp formats accepted by SQLite.\n\n    See https://sqlite.org/lang_datefunc.html#time_values for the full\n    list of datetime formats SQLite accepts.\n    \"\"\"\n\n    def result_processor(self, value, dialect):\n        return to_datetime\n\n\nclass Backend(BaseAlchemyBackend):\n    name = 'sqlite'\n    # TODO check if there is a reason to not use the parent AlchemyDatabase, or\n    # if there is technical debt that makes this required\n    database_class = Database\n    compiler = SQLiteCompiler\n\n    def __getstate__(self) -> dict:\n        r = super().__getstate__()\n        r.update(\n            dict(\n                compiler=self.compiler,\n                database_name=self.database_name,\n                _con=None,  # clear connection on copy()\n                _meta=None,\n            )\n        )\n        return r\n\n    def do_connect(\n        self,\n        database: str | Path | None = None,\n        path: str | Path | None = None,\n    ) -> None:\n        \"\"\"Create an Ibis client connected to a SQLite database.\n\n        Multiple database files can be accessed using the `attach()` method.\n\n        Parameters\n        ----------\n        database\n            File path to the SQLite database file. If `None`, creates an\n            in-memory transient database and you can use attach() to add more\n            files\n\n        Examples\n        --------\n        >>> import ibis\n        >>> ibis.sqlite.connect(\"path/to/my/sqlite.db\")\n        \"\"\"\n        if path is not None:\n            warnings.warn(\n                \"The `path` argument is deprecated in 4.0. Use `database=...`\"\n            )\n            database = path\n\n        self.database_name = \"main\"\n\n        engine = sa.create_engine(\n            f\"sqlite:///{database if database is not None else ':memory:'}\"\n        )\n\n        sqlite3.register_adapter(pd.Timestamp, lambda value: value.isoformat())\n\n        @sa.event.listens_for(engine, \"connect\")\n        def connect(dbapi_connection, connection_record):\n            \"\"\"Register UDFs on connection.\"\"\"\n            udf.register_all(dbapi_connection)\n\n        super().do_connect(engine)\n\n        @sa.event.listens_for(self.meta, \"column_reflect\")\n        def column_reflect(inspector, table, column_info):\n            if type(column_info[\"type\"]) is TIMESTAMP:\n                column_info[\"type\"] = ISODATETIME()\n\n    def attach(\n        self,\n        name: str,\n        path: str | Path,\n    ) -> None:\n        \"\"\"Connect another SQLite database file to the current connection.\n\n        Parameters\n        ----------\n        name\n            Database name within SQLite\n        path\n            Path to sqlite3 database file\n        \"\"\"\n        quoted_name = self.con.dialect.identifier_preparer.quote(name)\n        self.raw_sql(f\"ATTACH DATABASE {path!r} AS {quoted_name}\")\n\n    def _get_sqla_table(self, name, schema=None, autoload=True):\n        return sa.Table(\n            name,\n            self.meta,\n            schema=schema or self.current_database,\n            autoload=autoload,\n        )\n\n    def table(self, name: str, database: str | None = None) -> ir.Table:\n        \"\"\"Create a table expression from a table in the SQLite database.\n\n        Parameters\n        ----------\n        name\n            Table name\n        database\n            Name of the attached database that the table is located in.\n\n        Returns\n        -------\n        Table\n            Table expression\n        \"\"\"\n        alch_table = self._get_sqla_table(name, schema=database)\n        node = self.table_class(source=self, sqla_table=alch_table)\n        return self.table_expr_class(node)\n\n    def _table_from_schema(\n        self, name, schema, database: str | None = None\n    ) -> sa.Table:\n        columns = self._columns_from_schema(name, schema)\n        return sa.Table(name, self.meta, schema=database, *columns)\n\n    @property\n    def _current_schema(self) -> str | None:\n        return self.current_database\n", "path": "ibis/backends/sqlite/__init__.py"}], "after_files": [{"content": "# Copyright 2015 Cloudera Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom __future__ import annotations\n\nimport datetime\nimport sqlite3\nimport warnings\nfrom pathlib import Path\nfrom typing import TYPE_CHECKING\n\nimport pandas as pd\nimport sqlalchemy as sa\nfrom sqlalchemy.dialects.sqlite import DATETIME, TIMESTAMP\n\nif TYPE_CHECKING:\n    import ibis.expr.types as ir\n    import ibis.expr.datatypes as dt\n\nfrom ibis.backends.base import Database\nfrom ibis.backends.base.sql.alchemy import BaseAlchemyBackend, to_sqla_type\nfrom ibis.backends.sqlite import udf\nfrom ibis.backends.sqlite.compiler import SQLiteCompiler\nfrom ibis.expr.schema import datatype\n\n\ndef to_datetime(value: str | None) -> datetime.datetime | None:\n    \"\"\"Convert a str to a datetime according to SQLite's rules, ignoring `None`\n    values\"\"\"\n    if value is None:\n        return None\n    if value.endswith(\"Z\"):\n        # Parse and set the timezone as UTC\n        o = datetime.datetime.fromisoformat(value[:-1]).replace(\n            tzinfo=datetime.timezone.utc\n        )\n    else:\n        o = datetime.datetime.fromisoformat(value)\n        if o.tzinfo:\n            # Convert any aware datetime to UTC\n            return o.astimezone(datetime.timezone.utc)\n    return o\n\n\nclass ISODATETIME(DATETIME):\n    \"\"\"A thin datetime type to override sqlalchemy's datetime parsing to\n    support a wider range of timestamp formats accepted by SQLite.\n\n    See https://sqlite.org/lang_datefunc.html#time_values for the full\n    list of datetime formats SQLite accepts.\n    \"\"\"\n\n    def result_processor(self, value, dialect):\n        return to_datetime\n\n\nclass Backend(BaseAlchemyBackend):\n    name = 'sqlite'\n    # TODO check if there is a reason to not use the parent AlchemyDatabase, or\n    # if there is technical debt that makes this required\n    database_class = Database\n    compiler = SQLiteCompiler\n\n    def __getstate__(self) -> dict:\n        r = super().__getstate__()\n        r.update(\n            dict(\n                compiler=self.compiler,\n                database_name=self.database_name,\n                _con=None,  # clear connection on copy()\n                _meta=None,\n            )\n        )\n        return r\n\n    def do_connect(\n        self,\n        database: str | Path | None = None,\n        path: str | Path | None = None,\n        type_map: dict[str, str | dt.DataType] | None = None,\n    ) -> None:\n        \"\"\"Create an Ibis client connected to a SQLite database.\n\n        Multiple database files can be accessed using the `attach()` method.\n\n        Parameters\n        ----------\n        database\n            File path to the SQLite database file. If `None`, creates an\n            in-memory transient database and you can use attach() to add more\n            files\n        type_map\n            An optional mapping from a string name of a SQLite \"type\" to the\n            corresponding ibis DataType that it represents. This can be used\n            to override schema inference for a given SQLite database.\n\n        Examples\n        --------\n        >>> import ibis\n        >>> ibis.sqlite.connect(\"path/to/my/sqlite.db\")\n        \"\"\"\n        if path is not None:\n            warnings.warn(\n                \"The `path` argument is deprecated in 4.0. Use `database=...`\"\n            )\n            database = path\n\n        self.database_name = \"main\"\n\n        engine = sa.create_engine(\n            f\"sqlite:///{database if database is not None else ':memory:'}\"\n        )\n\n        if type_map:\n            # Patch out ischema_names for the instantiated dialect. This\n            # attribute is required for all SQLAlchemy dialects, but as no\n            # public way of modifying it for a given dialect. Patching seems\n            # easier than subclassing the builtin SQLite dialect, and achieves\n            # the same desired behavior.\n            def _to_ischema_val(t):\n                sa_type = to_sqla_type(datatype(t))\n                if isinstance(sa_type, sa.types.TypeEngine):\n                    # SQLAlchemy expects a callable here, rather than an\n                    # instance. Use a lambda to work around this.\n                    return lambda: sa_type\n                return sa_type\n\n            overrides = {k: _to_ischema_val(v) for k, v in type_map.items()}\n            engine.dialect.ischema_names = engine.dialect.ischema_names.copy()\n            engine.dialect.ischema_names.update(overrides)\n\n        sqlite3.register_adapter(pd.Timestamp, lambda value: value.isoformat())\n\n        @sa.event.listens_for(engine, \"connect\")\n        def connect(dbapi_connection, connection_record):\n            \"\"\"Register UDFs on connection.\"\"\"\n            udf.register_all(dbapi_connection)\n\n        super().do_connect(engine)\n\n        @sa.event.listens_for(self.meta, \"column_reflect\")\n        def column_reflect(inspector, table, column_info):\n            if type(column_info[\"type\"]) is TIMESTAMP:\n                column_info[\"type\"] = ISODATETIME()\n\n    def attach(\n        self,\n        name: str,\n        path: str | Path,\n    ) -> None:\n        \"\"\"Connect another SQLite database file to the current connection.\n\n        Parameters\n        ----------\n        name\n            Database name within SQLite\n        path\n            Path to sqlite3 database file\n        \"\"\"\n        quoted_name = self.con.dialect.identifier_preparer.quote(name)\n        self.raw_sql(f\"ATTACH DATABASE {path!r} AS {quoted_name}\")\n\n    def _get_sqla_table(self, name, schema=None, autoload=True):\n        return sa.Table(\n            name,\n            self.meta,\n            schema=schema or self.current_database,\n            autoload=autoload,\n        )\n\n    def table(self, name: str, database: str | None = None) -> ir.Table:\n        \"\"\"Create a table expression from a table in the SQLite database.\n\n        Parameters\n        ----------\n        name\n            Table name\n        database\n            Name of the attached database that the table is located in.\n\n        Returns\n        -------\n        Table\n            Table expression\n        \"\"\"\n        alch_table = self._get_sqla_table(name, schema=database)\n        node = self.table_class(source=self, sqla_table=alch_table)\n        return self.table_expr_class(node)\n\n    def _table_from_schema(\n        self, name, schema, database: str | None = None\n    ) -> sa.Table:\n        columns = self._columns_from_schema(name, schema)\n        return sa.Table(name, self.meta, schema=database, *columns)\n\n    @property\n    def _current_schema(self) -> str | None:\n        return self.current_database\n", "path": "ibis/backends/sqlite/__init__.py"}]}