We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Implement `tables.get` RPC method


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mathesar/rpc/tables.py`
Content:
```
1 from typing import Optional, TypedDict
2 
3 from modernrpc.core import rpc_method, REQUEST_KEY
4 from modernrpc.auth.basic import http_basic_auth_login_required
5 
6 from db.tables.operations.select import get_table_info
7 from db.tables.operations.drop import drop_table_from_database
8 from mathesar.rpc.exceptions.handlers import handle_rpc_exceptions
9 from mathesar.rpc.utils import connect
10 
11 
12 class TableInfo(TypedDict):
13     """
14     Information about a table.
15 
16     Attributes:
17         oid: The `oid` of the table in the schema.
18         name: The name of the table.
19         schema: The `oid` of the schema where the table lives.
20         description: The description of the table.
21     """
22     oid: int
23     name: str
24     schema: int
25     description: Optional[str]
26 
27 
28 @rpc_method(name="tables.list")
29 @http_basic_auth_login_required
30 @handle_rpc_exceptions
31 def list_(*, schema_oid: int, database_id: int, **kwargs) -> list[TableInfo]:
32     """
33     List information about tables for a schema. Exposed as `list`.
34 
35     Args:
36         schema_oid: Identity of the schema in the user's database.
37         database_id: The Django id of the database containing the table.
38 
39     Returns:
40         A list of table details.
41     """
42     user = kwargs.get(REQUEST_KEY).user
43     with connect(database_id, user) as conn:
44         raw_table_info = get_table_info(schema_oid, conn)
45     return [
46         TableInfo(tab) for tab in raw_table_info
47     ]
48 
49 
50 @rpc_method(name="tables.delete")
51 @http_basic_auth_login_required
52 @handle_rpc_exceptions
53 def delete(
54     *, table_oid: int, database_id: int, cascade: bool = False, **kwargs
55 ) -> str:
56     """
57     Delete a table from a schema.
58 
59     Args:
60         table_oid: Identity of the table in the user's database.
61         database_id: The Django id of the database containing the table.
62         cascade: Whether to drop the dependent objects.
63 
64     Returns:
65         The name of the dropped table.
66     """
67     user = kwargs.get(REQUEST_KEY).user
68     with connect(database_id, user) as conn:
69         return drop_table_from_database(table_oid, conn, cascade)
70
```

Path: `db/tables/operations/select.py`
Content:
```
1 from sqlalchemy import (
2     Table, select, join, inspect, and_, cast, func, Integer, literal, or_,
3 )
4 from sqlalchemy.dialects.postgresql import JSONB
5 
6 from db.connection import exec_msar_func
7 from db.utils import execute_statement, get_pg_catalog_table
8 
9 BASE = 'base'
10 DEPTH = 'depth'
11 JP_PATH = 'jp_path'
12 FK_PATH = 'fk_path'
13 REVERSE = 'reverse'
14 TARGET = 'target'
15 MULTIPLE_RESULTS = 'multiple_results'
16 
17 
18 def get_table_info(schema, conn):
19     """
20     Return a list of dictionaries describing the tables of a schema.
21 
22     The `schema` can be given as either a "qualified name", or an OID.
23     The OID is the preferred identifier, since it's much more robust.
24 
25     The returned list contains dictionaries of the following form:
26 
27         {
28             "oid": <int>,
29             "name": <str>,
30             "schema": <int>,
31             "description": <str>
32         }
33 
34     Args:
35         schema: The schema for which we want table info.
36     """
37     return exec_msar_func(conn, 'get_table_info', schema).fetchone()[0]
38 
39 
40 def reflect_table(name, schema, engine, metadata, connection_to_use=None, keep_existing=False):
41     extend_existing = not keep_existing
42     autoload_with = engine if connection_to_use is None else connection_to_use
43     return Table(
44         name,
45         metadata,
46         schema=schema,
47         autoload_with=autoload_with,
48         extend_existing=extend_existing,
49         keep_existing=keep_existing
50     )
51 
52 
53 def reflect_table_from_oid(oid, engine, metadata, connection_to_use=None, keep_existing=False):
54     tables = reflect_tables_from_oids(
55         [oid],
56         engine,
57         metadata=metadata,
58         connection_to_use=connection_to_use,
59         keep_existing=keep_existing
60     )
61     return tables.get(oid, None)
62 
63 
64 def reflect_tables_from_oids(oids, engine, metadata, connection_to_use=None, keep_existing=False):
65     oids_to_schema_and_table_names = (
66         get_map_of_table_oid_to_schema_name_and_table_name(
67             oids,
68             engine,
69             metadata=metadata,
70             connection_to_use=connection_to_use,
71         )
72     )
73     table_oids_to_sa_tables = {}
74     for table_oid, (schema_name, table_name) in oids_to_schema_and_table_names.items():
75         table_oids_to_sa_tables[table_oid] = reflect_table(
76             table_name,
77             schema_name,
78             engine,
79             metadata=metadata,
80             connection_to_use=connection_to_use,
81             keep_existing=keep_existing
82         )
83     return table_oids_to_sa_tables
84 
85 
86 def get_map_of_table_oid_to_schema_name_and_table_name(
87         table_oids,
88         engine,
89         metadata,
90         connection_to_use=None,
91 ):
92     if len(table_oids) == 0:
93         return {}
94     pg_class = get_pg_catalog_table("pg_class", engine, metadata=metadata)
95     pg_namespace = get_pg_catalog_table("pg_namespace", engine, metadata=metadata)
96     sel = (
97         select(pg_namespace.c.nspname, pg_class.c.relname, pg_class.c.oid)
98         .select_from(
99             join(
100                 pg_class,
101                 pg_namespace,
102                 pg_class.c.relnamespace == pg_namespace.c.oid
103             )
104         )
105         .where(pg_class.c.oid.in_(table_oids))
106     )
107     result_rows = execute_statement(engine, sel, connection_to_use).fetchall()
108     table_oids_to_schema_names_and_table_names = {
109         table_oid: (schema_name, table_name)
110         for schema_name, table_name, table_oid
111         in result_rows
112     }
113     return table_oids_to_schema_names_and_table_names
114 
115 
116 def get_table_oids_from_schemas(schema_oids, engine, metadata):
117     pg_class = get_pg_catalog_table("pg_class", engine, metadata)
118     sel = (
119         select(pg_class.c.oid, pg_class.c.relnamespace.label('schema_oid'))
120         .where(
121             and_(pg_class.c.relkind == 'r', pg_class.c.relnamespace.in_(schema_oids))
122         )
123     )
124     with engine.begin() as conn:
125         table_oids = conn.execute(sel).fetchall()
126     return table_oids
127 
128 
129 def get_oid_from_table(name, schema, engine):
130     inspector = inspect(engine)
131     return inspector.get_table_oid(name, schema=schema)
132 
133 
134 def get_table_description(oid, engine):
135     with engine.begin() as conn:
136         res = conn.execute(select(func.obj_description(oid, 'pg_class')))
137     return res.fetchone()[0]
138 
139 
140 def get_joinable_tables(
141     engine, metadata, base_table_oid=None, max_depth=3, limit=None, offset=None
142 ):
143     """
144     Output is a collection of `sqlalchemy.engine.Row`[0]. `Row` "seeks to act as much like a
145     Python named tuple as possible". Look ups can be done via integer indexes, string indexes or
146     attributes (e.g. `joinable_table[TARGET]. Inspect code to know what the exact keys are, but
147     their order and semantics is: `(base table oid, target table oid, join parameter path,
148     foreign key path, depth, multiple results boolean flag).
149 
150     [0] https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Row
151     """
152     FK_OID = 'fk_oid'
153     LEFT_REL = 'left_rel'
154     RIGHT_REL = 'right_rel'
155     LEFT_COL = 'left_col'
156     RIGHT_COL = 'right_col'
157 
158     SYMMETRIC_FKEYS = 'symmetric_fkeys'
159     SEARCH_FKEY_GRAPH = 'search_fkey_graph'
160     OUTPUT_CTE = 'output_cte'
161 
162     jba = func.jsonb_build_array
163 
164     pg_constraint = get_pg_catalog_table("pg_constraint", engine, metadata=metadata)
165 
166     symmetric_fkeys = select(
167         cast(pg_constraint.c.oid, Integer).label(FK_OID),
168         cast(pg_constraint.c.conrelid, Integer).label(LEFT_REL),
169         cast(pg_constraint.c.confrelid, Integer).label(RIGHT_REL),
170         cast(pg_constraint.c.conkey[1], Integer).label(LEFT_COL),
171         cast(pg_constraint.c.confkey[1], Integer).label(RIGHT_COL),
172         literal(False).label(MULTIPLE_RESULTS),
173         literal(False).label(REVERSE),
174     ).where(
175         and_(
176             pg_constraint.c.contype == 'f',
177             func.array_length(pg_constraint.c.conkey, 1) == 1
178         )
179     ).union_all(
180         select(
181             cast(pg_constraint.c.oid, Integer).label(FK_OID),
182             cast(pg_constraint.c.confrelid, Integer).label(LEFT_REL),
183             cast(pg_constraint.c.conrelid, Integer).label(RIGHT_REL),
184             cast(pg_constraint.c.confkey[1], Integer).label(LEFT_COL),
185             cast(pg_constraint.c.conkey[1], Integer).label(RIGHT_COL),
186             literal(True).label(MULTIPLE_RESULTS),
187             literal(True).label(REVERSE),
188         ).where(
189             and_(
190                 pg_constraint.c.contype == 'f',
191                 func.array_length(pg_constraint.c.conkey, 1) == 1
192             )
193         )
194     ).cte(name=SYMMETRIC_FKEYS)
195 
196     search_fkey_graph = select(
197         symmetric_fkeys.columns[LEFT_REL],
198         symmetric_fkeys.columns[RIGHT_REL],
199         symmetric_fkeys.columns[LEFT_COL],
200         symmetric_fkeys.columns[RIGHT_COL],
201         literal(1).label(DEPTH),
202         cast(
203             jba(
204                 jba(
205                     jba(
206                         symmetric_fkeys.columns[LEFT_REL],
207                         symmetric_fkeys.columns[LEFT_COL]
208                     ),
209                     jba(
210                         symmetric_fkeys.columns[RIGHT_REL],
211                         symmetric_fkeys.columns[RIGHT_COL]
212                     ),
213                 )
214             ),
215             JSONB
216         ).label(JP_PATH),
217         cast(
218             jba(
219                 jba(
220                     symmetric_fkeys.columns[FK_OID],
221                     symmetric_fkeys.columns[REVERSE],
222                 )
223             ),
224             JSONB
225         ).label(FK_PATH),
226         symmetric_fkeys.columns[MULTIPLE_RESULTS],
227     ).cte(name=SEARCH_FKEY_GRAPH, recursive=True)
228 
229     search_fkey_graph = search_fkey_graph.union_all(
230         select(
231             symmetric_fkeys.columns[LEFT_REL],
232             symmetric_fkeys.columns[RIGHT_REL],
233             symmetric_fkeys.columns[LEFT_COL],
234             symmetric_fkeys.columns[RIGHT_COL],
235             search_fkey_graph.columns[DEPTH] + 1,
236             search_fkey_graph.columns[JP_PATH] + cast(
237                 jba(
238                     jba(
239                         jba(
240                             symmetric_fkeys.columns[LEFT_REL],
241                             symmetric_fkeys.columns[LEFT_COL]
242                         ),
243                         jba(
244                             symmetric_fkeys.columns[RIGHT_REL],
245                             symmetric_fkeys.columns[RIGHT_COL]
246                         ),
247                     )
248                 ),
249                 JSONB
250             ),
251             search_fkey_graph.columns[FK_PATH] + cast(
252                 jba(
253                     jba(
254                         symmetric_fkeys.columns[FK_OID],
255                         symmetric_fkeys.columns[REVERSE],
256                     )
257                 ),
258                 JSONB
259             ),
260             or_(
261                 search_fkey_graph.columns[MULTIPLE_RESULTS],
262                 symmetric_fkeys.columns[MULTIPLE_RESULTS]
263             )
264         ).where(
265             and_(
266                 symmetric_fkeys.columns[LEFT_REL] == search_fkey_graph.columns[RIGHT_REL],
267                 search_fkey_graph.columns[DEPTH] < max_depth,
268                 search_fkey_graph.columns[JP_PATH][-1] != jba(
269                     jba(
270                         symmetric_fkeys.columns[RIGHT_REL],
271                         symmetric_fkeys.columns[RIGHT_COL]
272                     ),
273                     jba(
274                         symmetric_fkeys.columns[LEFT_REL],
275                         symmetric_fkeys.columns[LEFT_COL]
276                     ),
277                 )
278             )
279         )
280     )
281 
282     output_cte = select(
283         cast(search_fkey_graph.columns[JP_PATH][0][0][0], Integer).label(BASE),
284         cast(search_fkey_graph.columns[JP_PATH][-1][-1][0], Integer).label(TARGET),
285         search_fkey_graph.columns[JP_PATH].label(JP_PATH),
286         search_fkey_graph.columns[FK_PATH].label(FK_PATH),
287         search_fkey_graph.columns[DEPTH].label(DEPTH),
288         search_fkey_graph.columns[MULTIPLE_RESULTS].label(MULTIPLE_RESULTS)
289     ).cte(name=OUTPUT_CTE)
290 
291     if base_table_oid is not None:
292         final_sel = select(output_cte).where(
293             output_cte.columns[BASE] == base_table_oid
294         )
295     else:
296         final_sel = select(output_cte)
297 
298     with engine.begin() as conn:
299         results = conn.execute(final_sel.limit(limit).offset(offset)).fetchall()
300 
301     return results
302
```
--- 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/db/tables/operations/select.py b/db/tables/operations/select.py
--- a/db/tables/operations/select.py
+++ b/db/tables/operations/select.py
@@ -15,6 +15,28 @@
 MULTIPLE_RESULTS = 'multiple_results'
 
 
+def get_table(table, conn):
+    """
+    Return a dictionary describing a table of a schema.
+
+    The `table` can be given as either a "qualified name", or an OID.
+    The OID is the preferred identifier, since it's much more robust.
+
+    The returned dictionary is of the following form:
+
+        {
+            "oid": <int>,
+            "name": <str>,
+            "schema": <int>,
+            "description": <str>
+        }
+
+    Args:
+        table: The table for which we want table info.
+    """
+    return exec_msar_func(conn, 'get_table', table).fetchone()[0]
+
+
 def get_table_info(schema, conn):
     """
     Return a list of dictionaries describing the tables of a schema.
diff --git a/mathesar/rpc/tables.py b/mathesar/rpc/tables.py
--- a/mathesar/rpc/tables.py
+++ b/mathesar/rpc/tables.py
@@ -3,7 +3,7 @@
 from modernrpc.core import rpc_method, REQUEST_KEY
 from modernrpc.auth.basic import http_basic_auth_login_required
 
-from db.tables.operations.select import get_table_info
+from db.tables.operations.select import get_table_info, get_table
 from db.tables.operations.drop import drop_table_from_database
 from mathesar.rpc.exceptions.handlers import handle_rpc_exceptions
 from mathesar.rpc.utils import connect
@@ -47,6 +47,26 @@
     ]
 
 
+@rpc_method(name="tables.get")
+@http_basic_auth_login_required
+@handle_rpc_exceptions
+def get(*, table_oid: int, database_id: int, **kwargs) -> TableInfo:
+    """
+    List information about a table for a schema.
+
+    Args:
+        table_oid: Identity of the table in the user's database.
+        database_id: The Django id of the database containing the table.
+
+    Returns:
+        Table details for a given table oid.
+    """
+    user = kwargs.get(REQUEST_KEY).user
+    with connect(database_id, user) as conn:
+        raw_table_info = get_table(table_oid, conn)
+    return TableInfo(raw_table_info)
+
+
 @rpc_method(name="tables.delete")
 @http_basic_auth_login_required
 @handle_rpc_exceptions

{"golden_diff": "diff --git a/db/tables/operations/select.py b/db/tables/operations/select.py\n--- a/db/tables/operations/select.py\n+++ b/db/tables/operations/select.py\n@@ -15,6 +15,28 @@\n MULTIPLE_RESULTS = 'multiple_results'\n \n \n+def get_table(table, conn):\n+    \"\"\"\n+    Return a dictionary describing a table of a schema.\n+\n+    The `table` can be given as either a \"qualified name\", or an OID.\n+    The OID is the preferred identifier, since it's much more robust.\n+\n+    The returned dictionary is of the following form:\n+\n+        {\n+            \"oid\": <int>,\n+            \"name\": <str>,\n+            \"schema\": <int>,\n+            \"description\": <str>\n+        }\n+\n+    Args:\n+        table: The table for which we want table info.\n+    \"\"\"\n+    return exec_msar_func(conn, 'get_table', table).fetchone()[0]\n+\n+\n def get_table_info(schema, conn):\n     \"\"\"\n     Return a list of dictionaries describing the tables of a schema.\ndiff --git a/mathesar/rpc/tables.py b/mathesar/rpc/tables.py\n--- a/mathesar/rpc/tables.py\n+++ b/mathesar/rpc/tables.py\n@@ -3,7 +3,7 @@\n from modernrpc.core import rpc_method, REQUEST_KEY\n from modernrpc.auth.basic import http_basic_auth_login_required\n \n-from db.tables.operations.select import get_table_info\n+from db.tables.operations.select import get_table_info, get_table\n from db.tables.operations.drop import drop_table_from_database\n from mathesar.rpc.exceptions.handlers import handle_rpc_exceptions\n from mathesar.rpc.utils import connect\n@@ -47,6 +47,26 @@\n     ]\n \n \n+@rpc_method(name=\"tables.get\")\n+@http_basic_auth_login_required\n+@handle_rpc_exceptions\n+def get(*, table_oid: int, database_id: int, **kwargs) -> TableInfo:\n+    \"\"\"\n+    List information about a table for a schema.\n+\n+    Args:\n+        table_oid: Identity of the table in the user's database.\n+        database_id: The Django id of the database containing the table.\n+\n+    Returns:\n+        Table details for a given table oid.\n+    \"\"\"\n+    user = kwargs.get(REQUEST_KEY).user\n+    with connect(database_id, user) as conn:\n+        raw_table_info = get_table(table_oid, conn)\n+    return TableInfo(raw_table_info)\n+\n+\n @rpc_method(name=\"tables.delete\")\n @http_basic_auth_login_required\n @handle_rpc_exceptions\n", "issue": "Implement `tables.get` RPC method\n\n", "before_files": [{"content": "from typing import Optional, TypedDict\n\nfrom modernrpc.core import rpc_method, REQUEST_KEY\nfrom modernrpc.auth.basic import http_basic_auth_login_required\n\nfrom db.tables.operations.select import get_table_info\nfrom db.tables.operations.drop import drop_table_from_database\nfrom mathesar.rpc.exceptions.handlers import handle_rpc_exceptions\nfrom mathesar.rpc.utils import connect\n\n\nclass TableInfo(TypedDict):\n    \"\"\"\n    Information about a table.\n\n    Attributes:\n        oid: The `oid` of the table in the schema.\n        name: The name of the table.\n        schema: The `oid` of the schema where the table lives.\n        description: The description of the table.\n    \"\"\"\n    oid: int\n    name: str\n    schema: int\n    description: Optional[str]\n\n\n@rpc_method(name=\"tables.list\")\n@http_basic_auth_login_required\n@handle_rpc_exceptions\ndef list_(*, schema_oid: int, database_id: int, **kwargs) -> list[TableInfo]:\n    \"\"\"\n    List information about tables for a schema. Exposed as `list`.\n\n    Args:\n        schema_oid: Identity of the schema in the user's database.\n        database_id: The Django id of the database containing the table.\n\n    Returns:\n        A list of table details.\n    \"\"\"\n    user = kwargs.get(REQUEST_KEY).user\n    with connect(database_id, user) as conn:\n        raw_table_info = get_table_info(schema_oid, conn)\n    return [\n        TableInfo(tab) for tab in raw_table_info\n    ]\n\n\n@rpc_method(name=\"tables.delete\")\n@http_basic_auth_login_required\n@handle_rpc_exceptions\ndef delete(\n    *, table_oid: int, database_id: int, cascade: bool = False, **kwargs\n) -> str:\n    \"\"\"\n    Delete a table from a schema.\n\n    Args:\n        table_oid: Identity of the table in the user's database.\n        database_id: The Django id of the database containing the table.\n        cascade: Whether to drop the dependent objects.\n\n    Returns:\n        The name of the dropped table.\n    \"\"\"\n    user = kwargs.get(REQUEST_KEY).user\n    with connect(database_id, user) as conn:\n        return drop_table_from_database(table_oid, conn, cascade)\n", "path": "mathesar/rpc/tables.py"}, {"content": "from sqlalchemy import (\n    Table, select, join, inspect, and_, cast, func, Integer, literal, or_,\n)\nfrom sqlalchemy.dialects.postgresql import JSONB\n\nfrom db.connection import exec_msar_func\nfrom db.utils import execute_statement, get_pg_catalog_table\n\nBASE = 'base'\nDEPTH = 'depth'\nJP_PATH = 'jp_path'\nFK_PATH = 'fk_path'\nREVERSE = 'reverse'\nTARGET = 'target'\nMULTIPLE_RESULTS = 'multiple_results'\n\n\ndef get_table_info(schema, conn):\n    \"\"\"\n    Return a list of dictionaries describing the tables of a schema.\n\n    The `schema` can be given as either a \"qualified name\", or an OID.\n    The OID is the preferred identifier, since it's much more robust.\n\n    The returned list contains dictionaries of the following form:\n\n        {\n            \"oid\": <int>,\n            \"name\": <str>,\n            \"schema\": <int>,\n            \"description\": <str>\n        }\n\n    Args:\n        schema: The schema for which we want table info.\n    \"\"\"\n    return exec_msar_func(conn, 'get_table_info', schema).fetchone()[0]\n\n\ndef reflect_table(name, schema, engine, metadata, connection_to_use=None, keep_existing=False):\n    extend_existing = not keep_existing\n    autoload_with = engine if connection_to_use is None else connection_to_use\n    return Table(\n        name,\n        metadata,\n        schema=schema,\n        autoload_with=autoload_with,\n        extend_existing=extend_existing,\n        keep_existing=keep_existing\n    )\n\n\ndef reflect_table_from_oid(oid, engine, metadata, connection_to_use=None, keep_existing=False):\n    tables = reflect_tables_from_oids(\n        [oid],\n        engine,\n        metadata=metadata,\n        connection_to_use=connection_to_use,\n        keep_existing=keep_existing\n    )\n    return tables.get(oid, None)\n\n\ndef reflect_tables_from_oids(oids, engine, metadata, connection_to_use=None, keep_existing=False):\n    oids_to_schema_and_table_names = (\n        get_map_of_table_oid_to_schema_name_and_table_name(\n            oids,\n            engine,\n            metadata=metadata,\n            connection_to_use=connection_to_use,\n        )\n    )\n    table_oids_to_sa_tables = {}\n    for table_oid, (schema_name, table_name) in oids_to_schema_and_table_names.items():\n        table_oids_to_sa_tables[table_oid] = reflect_table(\n            table_name,\n            schema_name,\n            engine,\n            metadata=metadata,\n            connection_to_use=connection_to_use,\n            keep_existing=keep_existing\n        )\n    return table_oids_to_sa_tables\n\n\ndef get_map_of_table_oid_to_schema_name_and_table_name(\n        table_oids,\n        engine,\n        metadata,\n        connection_to_use=None,\n):\n    if len(table_oids) == 0:\n        return {}\n    pg_class = get_pg_catalog_table(\"pg_class\", engine, metadata=metadata)\n    pg_namespace = get_pg_catalog_table(\"pg_namespace\", engine, metadata=metadata)\n    sel = (\n        select(pg_namespace.c.nspname, pg_class.c.relname, pg_class.c.oid)\n        .select_from(\n            join(\n                pg_class,\n                pg_namespace,\n                pg_class.c.relnamespace == pg_namespace.c.oid\n            )\n        )\n        .where(pg_class.c.oid.in_(table_oids))\n    )\n    result_rows = execute_statement(engine, sel, connection_to_use).fetchall()\n    table_oids_to_schema_names_and_table_names = {\n        table_oid: (schema_name, table_name)\n        for schema_name, table_name, table_oid\n        in result_rows\n    }\n    return table_oids_to_schema_names_and_table_names\n\n\ndef get_table_oids_from_schemas(schema_oids, engine, metadata):\n    pg_class = get_pg_catalog_table(\"pg_class\", engine, metadata)\n    sel = (\n        select(pg_class.c.oid, pg_class.c.relnamespace.label('schema_oid'))\n        .where(\n            and_(pg_class.c.relkind == 'r', pg_class.c.relnamespace.in_(schema_oids))\n        )\n    )\n    with engine.begin() as conn:\n        table_oids = conn.execute(sel).fetchall()\n    return table_oids\n\n\ndef get_oid_from_table(name, schema, engine):\n    inspector = inspect(engine)\n    return inspector.get_table_oid(name, schema=schema)\n\n\ndef get_table_description(oid, engine):\n    with engine.begin() as conn:\n        res = conn.execute(select(func.obj_description(oid, 'pg_class')))\n    return res.fetchone()[0]\n\n\ndef get_joinable_tables(\n    engine, metadata, base_table_oid=None, max_depth=3, limit=None, offset=None\n):\n    \"\"\"\n    Output is a collection of `sqlalchemy.engine.Row`[0]. `Row` \"seeks to act as much like a\n    Python named tuple as possible\". Look ups can be done via integer indexes, string indexes or\n    attributes (e.g. `joinable_table[TARGET]. Inspect code to know what the exact keys are, but\n    their order and semantics is: `(base table oid, target table oid, join parameter path,\n    foreign key path, depth, multiple results boolean flag).\n\n    [0] https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Row\n    \"\"\"\n    FK_OID = 'fk_oid'\n    LEFT_REL = 'left_rel'\n    RIGHT_REL = 'right_rel'\n    LEFT_COL = 'left_col'\n    RIGHT_COL = 'right_col'\n\n    SYMMETRIC_FKEYS = 'symmetric_fkeys'\n    SEARCH_FKEY_GRAPH = 'search_fkey_graph'\n    OUTPUT_CTE = 'output_cte'\n\n    jba = func.jsonb_build_array\n\n    pg_constraint = get_pg_catalog_table(\"pg_constraint\", engine, metadata=metadata)\n\n    symmetric_fkeys = select(\n        cast(pg_constraint.c.oid, Integer).label(FK_OID),\n        cast(pg_constraint.c.conrelid, Integer).label(LEFT_REL),\n        cast(pg_constraint.c.confrelid, Integer).label(RIGHT_REL),\n        cast(pg_constraint.c.conkey[1], Integer).label(LEFT_COL),\n        cast(pg_constraint.c.confkey[1], Integer).label(RIGHT_COL),\n        literal(False).label(MULTIPLE_RESULTS),\n        literal(False).label(REVERSE),\n    ).where(\n        and_(\n            pg_constraint.c.contype == 'f',\n            func.array_length(pg_constraint.c.conkey, 1) == 1\n        )\n    ).union_all(\n        select(\n            cast(pg_constraint.c.oid, Integer).label(FK_OID),\n            cast(pg_constraint.c.confrelid, Integer).label(LEFT_REL),\n            cast(pg_constraint.c.conrelid, Integer).label(RIGHT_REL),\n            cast(pg_constraint.c.confkey[1], Integer).label(LEFT_COL),\n            cast(pg_constraint.c.conkey[1], Integer).label(RIGHT_COL),\n            literal(True).label(MULTIPLE_RESULTS),\n            literal(True).label(REVERSE),\n        ).where(\n            and_(\n                pg_constraint.c.contype == 'f',\n                func.array_length(pg_constraint.c.conkey, 1) == 1\n            )\n        )\n    ).cte(name=SYMMETRIC_FKEYS)\n\n    search_fkey_graph = select(\n        symmetric_fkeys.columns[LEFT_REL],\n        symmetric_fkeys.columns[RIGHT_REL],\n        symmetric_fkeys.columns[LEFT_COL],\n        symmetric_fkeys.columns[RIGHT_COL],\n        literal(1).label(DEPTH),\n        cast(\n            jba(\n                jba(\n                    jba(\n                        symmetric_fkeys.columns[LEFT_REL],\n                        symmetric_fkeys.columns[LEFT_COL]\n                    ),\n                    jba(\n                        symmetric_fkeys.columns[RIGHT_REL],\n                        symmetric_fkeys.columns[RIGHT_COL]\n                    ),\n                )\n            ),\n            JSONB\n        ).label(JP_PATH),\n        cast(\n            jba(\n                jba(\n                    symmetric_fkeys.columns[FK_OID],\n                    symmetric_fkeys.columns[REVERSE],\n                )\n            ),\n            JSONB\n        ).label(FK_PATH),\n        symmetric_fkeys.columns[MULTIPLE_RESULTS],\n    ).cte(name=SEARCH_FKEY_GRAPH, recursive=True)\n\n    search_fkey_graph = search_fkey_graph.union_all(\n        select(\n            symmetric_fkeys.columns[LEFT_REL],\n            symmetric_fkeys.columns[RIGHT_REL],\n            symmetric_fkeys.columns[LEFT_COL],\n            symmetric_fkeys.columns[RIGHT_COL],\n            search_fkey_graph.columns[DEPTH] + 1,\n            search_fkey_graph.columns[JP_PATH] + cast(\n                jba(\n                    jba(\n                        jba(\n                            symmetric_fkeys.columns[LEFT_REL],\n                            symmetric_fkeys.columns[LEFT_COL]\n                        ),\n                        jba(\n                            symmetric_fkeys.columns[RIGHT_REL],\n                            symmetric_fkeys.columns[RIGHT_COL]\n                        ),\n                    )\n                ),\n                JSONB\n            ),\n            search_fkey_graph.columns[FK_PATH] + cast(\n                jba(\n                    jba(\n                        symmetric_fkeys.columns[FK_OID],\n                        symmetric_fkeys.columns[REVERSE],\n                    )\n                ),\n                JSONB\n            ),\n            or_(\n                search_fkey_graph.columns[MULTIPLE_RESULTS],\n                symmetric_fkeys.columns[MULTIPLE_RESULTS]\n            )\n        ).where(\n            and_(\n                symmetric_fkeys.columns[LEFT_REL] == search_fkey_graph.columns[RIGHT_REL],\n                search_fkey_graph.columns[DEPTH] < max_depth,\n                search_fkey_graph.columns[JP_PATH][-1] != jba(\n                    jba(\n                        symmetric_fkeys.columns[RIGHT_REL],\n                        symmetric_fkeys.columns[RIGHT_COL]\n                    ),\n                    jba(\n                        symmetric_fkeys.columns[LEFT_REL],\n                        symmetric_fkeys.columns[LEFT_COL]\n                    ),\n                )\n            )\n        )\n    )\n\n    output_cte = select(\n        cast(search_fkey_graph.columns[JP_PATH][0][0][0], Integer).label(BASE),\n        cast(search_fkey_graph.columns[JP_PATH][-1][-1][0], Integer).label(TARGET),\n        search_fkey_graph.columns[JP_PATH].label(JP_PATH),\n        search_fkey_graph.columns[FK_PATH].label(FK_PATH),\n        search_fkey_graph.columns[DEPTH].label(DEPTH),\n        search_fkey_graph.columns[MULTIPLE_RESULTS].label(MULTIPLE_RESULTS)\n    ).cte(name=OUTPUT_CTE)\n\n    if base_table_oid is not None:\n        final_sel = select(output_cte).where(\n            output_cte.columns[BASE] == base_table_oid\n        )\n    else:\n        final_sel = select(output_cte)\n\n    with engine.begin() as conn:\n        results = conn.execute(final_sel.limit(limit).offset(offset)).fetchall()\n\n    return results\n", "path": "db/tables/operations/select.py"}], "after_files": [{"content": "from typing import Optional, TypedDict\n\nfrom modernrpc.core import rpc_method, REQUEST_KEY\nfrom modernrpc.auth.basic import http_basic_auth_login_required\n\nfrom db.tables.operations.select import get_table_info, get_table\nfrom db.tables.operations.drop import drop_table_from_database\nfrom mathesar.rpc.exceptions.handlers import handle_rpc_exceptions\nfrom mathesar.rpc.utils import connect\n\n\nclass TableInfo(TypedDict):\n    \"\"\"\n    Information about a table.\n\n    Attributes:\n        oid: The `oid` of the table in the schema.\n        name: The name of the table.\n        schema: The `oid` of the schema where the table lives.\n        description: The description of the table.\n    \"\"\"\n    oid: int\n    name: str\n    schema: int\n    description: Optional[str]\n\n\n@rpc_method(name=\"tables.list\")\n@http_basic_auth_login_required\n@handle_rpc_exceptions\ndef list_(*, schema_oid: int, database_id: int, **kwargs) -> list[TableInfo]:\n    \"\"\"\n    List information about tables for a schema. Exposed as `list`.\n\n    Args:\n        schema_oid: Identity of the schema in the user's database.\n        database_id: The Django id of the database containing the table.\n\n    Returns:\n        A list of table details.\n    \"\"\"\n    user = kwargs.get(REQUEST_KEY).user\n    with connect(database_id, user) as conn:\n        raw_table_info = get_table_info(schema_oid, conn)\n    return [\n        TableInfo(tab) for tab in raw_table_info\n    ]\n\n\n@rpc_method(name=\"tables.get\")\n@http_basic_auth_login_required\n@handle_rpc_exceptions\ndef get(*, table_oid: int, database_id: int, **kwargs) -> TableInfo:\n    \"\"\"\n    List information about a table for a schema.\n\n    Args:\n        table_oid: Identity of the table in the user's database.\n        database_id: The Django id of the database containing the table.\n\n    Returns:\n        Table details for a given table oid.\n    \"\"\"\n    user = kwargs.get(REQUEST_KEY).user\n    with connect(database_id, user) as conn:\n        raw_table_info = get_table(table_oid, conn)\n    return TableInfo(raw_table_info)\n\n\n@rpc_method(name=\"tables.delete\")\n@http_basic_auth_login_required\n@handle_rpc_exceptions\ndef delete(\n    *, table_oid: int, database_id: int, cascade: bool = False, **kwargs\n) -> str:\n    \"\"\"\n    Delete a table from a schema.\n\n    Args:\n        table_oid: Identity of the table in the user's database.\n        database_id: The Django id of the database containing the table.\n        cascade: Whether to drop the dependent objects.\n\n    Returns:\n        The name of the dropped table.\n    \"\"\"\n    user = kwargs.get(REQUEST_KEY).user\n    with connect(database_id, user) as conn:\n        return drop_table_from_database(table_oid, conn, cascade)\n", "path": "mathesar/rpc/tables.py"}, {"content": "from sqlalchemy import (\n    Table, select, join, inspect, and_, cast, func, Integer, literal, or_,\n)\nfrom sqlalchemy.dialects.postgresql import JSONB\n\nfrom db.connection import exec_msar_func\nfrom db.utils import execute_statement, get_pg_catalog_table\n\nBASE = 'base'\nDEPTH = 'depth'\nJP_PATH = 'jp_path'\nFK_PATH = 'fk_path'\nREVERSE = 'reverse'\nTARGET = 'target'\nMULTIPLE_RESULTS = 'multiple_results'\n\n\ndef get_table(table, conn):\n    \"\"\"\n    Return a dictionary describing a table of a schema.\n\n    The `table` can be given as either a \"qualified name\", or an OID.\n    The OID is the preferred identifier, since it's much more robust.\n\n    The returned dictionary is of the following form:\n\n        {\n            \"oid\": <int>,\n            \"name\": <str>,\n            \"schema\": <int>,\n            \"description\": <str>\n        }\n\n    Args:\n        table: The table for which we want table info.\n    \"\"\"\n    return exec_msar_func(conn, 'get_table', table).fetchone()[0]\n\n\ndef get_table_info(schema, conn):\n    \"\"\"\n    Return a list of dictionaries describing the tables of a schema.\n\n    The `schema` can be given as either a \"qualified name\", or an OID.\n    The OID is the preferred identifier, since it's much more robust.\n\n    The returned list contains dictionaries of the following form:\n\n        {\n            \"oid\": <int>,\n            \"name\": <str>,\n            \"schema\": <int>,\n            \"description\": <str>\n        }\n\n    Args:\n        schema: The schema for which we want table info.\n    \"\"\"\n    return exec_msar_func(conn, 'get_table_info', schema).fetchone()[0]\n\n\ndef reflect_table(name, schema, engine, metadata, connection_to_use=None, keep_existing=False):\n    extend_existing = not keep_existing\n    autoload_with = engine if connection_to_use is None else connection_to_use\n    return Table(\n        name,\n        metadata,\n        schema=schema,\n        autoload_with=autoload_with,\n        extend_existing=extend_existing,\n        keep_existing=keep_existing\n    )\n\n\ndef reflect_table_from_oid(oid, engine, metadata, connection_to_use=None, keep_existing=False):\n    tables = reflect_tables_from_oids(\n        [oid],\n        engine,\n        metadata=metadata,\n        connection_to_use=connection_to_use,\n        keep_existing=keep_existing\n    )\n    return tables.get(oid, None)\n\n\ndef reflect_tables_from_oids(oids, engine, metadata, connection_to_use=None, keep_existing=False):\n    oids_to_schema_and_table_names = (\n        get_map_of_table_oid_to_schema_name_and_table_name(\n            oids,\n            engine,\n            metadata=metadata,\n            connection_to_use=connection_to_use,\n        )\n    )\n    table_oids_to_sa_tables = {}\n    for table_oid, (schema_name, table_name) in oids_to_schema_and_table_names.items():\n        table_oids_to_sa_tables[table_oid] = reflect_table(\n            table_name,\n            schema_name,\n            engine,\n            metadata=metadata,\n            connection_to_use=connection_to_use,\n            keep_existing=keep_existing\n        )\n    return table_oids_to_sa_tables\n\n\ndef get_map_of_table_oid_to_schema_name_and_table_name(\n        table_oids,\n        engine,\n        metadata,\n        connection_to_use=None,\n):\n    if len(table_oids) == 0:\n        return {}\n    pg_class = get_pg_catalog_table(\"pg_class\", engine, metadata=metadata)\n    pg_namespace = get_pg_catalog_table(\"pg_namespace\", engine, metadata=metadata)\n    sel = (\n        select(pg_namespace.c.nspname, pg_class.c.relname, pg_class.c.oid)\n        .select_from(\n            join(\n                pg_class,\n                pg_namespace,\n                pg_class.c.relnamespace == pg_namespace.c.oid\n            )\n        )\n        .where(pg_class.c.oid.in_(table_oids))\n    )\n    result_rows = execute_statement(engine, sel, connection_to_use).fetchall()\n    table_oids_to_schema_names_and_table_names = {\n        table_oid: (schema_name, table_name)\n        for schema_name, table_name, table_oid\n        in result_rows\n    }\n    return table_oids_to_schema_names_and_table_names\n\n\ndef get_table_oids_from_schemas(schema_oids, engine, metadata):\n    pg_class = get_pg_catalog_table(\"pg_class\", engine, metadata)\n    sel = (\n        select(pg_class.c.oid, pg_class.c.relnamespace.label('schema_oid'))\n        .where(\n            and_(pg_class.c.relkind == 'r', pg_class.c.relnamespace.in_(schema_oids))\n        )\n    )\n    with engine.begin() as conn:\n        table_oids = conn.execute(sel).fetchall()\n    return table_oids\n\n\ndef get_oid_from_table(name, schema, engine):\n    inspector = inspect(engine)\n    return inspector.get_table_oid(name, schema=schema)\n\n\ndef get_table_description(oid, engine):\n    with engine.begin() as conn:\n        res = conn.execute(select(func.obj_description(oid, 'pg_class')))\n    return res.fetchone()[0]\n\n\ndef get_joinable_tables(\n    engine, metadata, base_table_oid=None, max_depth=3, limit=None, offset=None\n):\n    \"\"\"\n    Output is a collection of `sqlalchemy.engine.Row`[0]. `Row` \"seeks to act as much like a\n    Python named tuple as possible\". Look ups can be done via integer indexes, string indexes or\n    attributes (e.g. `joinable_table[TARGET]. Inspect code to know what the exact keys are, but\n    their order and semantics is: `(base table oid, target table oid, join parameter path,\n    foreign key path, depth, multiple results boolean flag).\n\n    [0] https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Row\n    \"\"\"\n    FK_OID = 'fk_oid'\n    LEFT_REL = 'left_rel'\n    RIGHT_REL = 'right_rel'\n    LEFT_COL = 'left_col'\n    RIGHT_COL = 'right_col'\n\n    SYMMETRIC_FKEYS = 'symmetric_fkeys'\n    SEARCH_FKEY_GRAPH = 'search_fkey_graph'\n    OUTPUT_CTE = 'output_cte'\n\n    jba = func.jsonb_build_array\n\n    pg_constraint = get_pg_catalog_table(\"pg_constraint\", engine, metadata=metadata)\n\n    symmetric_fkeys = select(\n        cast(pg_constraint.c.oid, Integer).label(FK_OID),\n        cast(pg_constraint.c.conrelid, Integer).label(LEFT_REL),\n        cast(pg_constraint.c.confrelid, Integer).label(RIGHT_REL),\n        cast(pg_constraint.c.conkey[1], Integer).label(LEFT_COL),\n        cast(pg_constraint.c.confkey[1], Integer).label(RIGHT_COL),\n        literal(False).label(MULTIPLE_RESULTS),\n        literal(False).label(REVERSE),\n    ).where(\n        and_(\n            pg_constraint.c.contype == 'f',\n            func.array_length(pg_constraint.c.conkey, 1) == 1\n        )\n    ).union_all(\n        select(\n            cast(pg_constraint.c.oid, Integer).label(FK_OID),\n            cast(pg_constraint.c.confrelid, Integer).label(LEFT_REL),\n            cast(pg_constraint.c.conrelid, Integer).label(RIGHT_REL),\n            cast(pg_constraint.c.confkey[1], Integer).label(LEFT_COL),\n            cast(pg_constraint.c.conkey[1], Integer).label(RIGHT_COL),\n            literal(True).label(MULTIPLE_RESULTS),\n            literal(True).label(REVERSE),\n        ).where(\n            and_(\n                pg_constraint.c.contype == 'f',\n                func.array_length(pg_constraint.c.conkey, 1) == 1\n            )\n        )\n    ).cte(name=SYMMETRIC_FKEYS)\n\n    search_fkey_graph = select(\n        symmetric_fkeys.columns[LEFT_REL],\n        symmetric_fkeys.columns[RIGHT_REL],\n        symmetric_fkeys.columns[LEFT_COL],\n        symmetric_fkeys.columns[RIGHT_COL],\n        literal(1).label(DEPTH),\n        cast(\n            jba(\n                jba(\n                    jba(\n                        symmetric_fkeys.columns[LEFT_REL],\n                        symmetric_fkeys.columns[LEFT_COL]\n                    ),\n                    jba(\n                        symmetric_fkeys.columns[RIGHT_REL],\n                        symmetric_fkeys.columns[RIGHT_COL]\n                    ),\n                )\n            ),\n            JSONB\n        ).label(JP_PATH),\n        cast(\n            jba(\n                jba(\n                    symmetric_fkeys.columns[FK_OID],\n                    symmetric_fkeys.columns[REVERSE],\n                )\n            ),\n            JSONB\n        ).label(FK_PATH),\n        symmetric_fkeys.columns[MULTIPLE_RESULTS],\n    ).cte(name=SEARCH_FKEY_GRAPH, recursive=True)\n\n    search_fkey_graph = search_fkey_graph.union_all(\n        select(\n            symmetric_fkeys.columns[LEFT_REL],\n            symmetric_fkeys.columns[RIGHT_REL],\n            symmetric_fkeys.columns[LEFT_COL],\n            symmetric_fkeys.columns[RIGHT_COL],\n            search_fkey_graph.columns[DEPTH] + 1,\n            search_fkey_graph.columns[JP_PATH] + cast(\n                jba(\n                    jba(\n                        jba(\n                            symmetric_fkeys.columns[LEFT_REL],\n                            symmetric_fkeys.columns[LEFT_COL]\n                        ),\n                        jba(\n                            symmetric_fkeys.columns[RIGHT_REL],\n                            symmetric_fkeys.columns[RIGHT_COL]\n                        ),\n                    )\n                ),\n                JSONB\n            ),\n            search_fkey_graph.columns[FK_PATH] + cast(\n                jba(\n                    jba(\n                        symmetric_fkeys.columns[FK_OID],\n                        symmetric_fkeys.columns[REVERSE],\n                    )\n                ),\n                JSONB\n            ),\n            or_(\n                search_fkey_graph.columns[MULTIPLE_RESULTS],\n                symmetric_fkeys.columns[MULTIPLE_RESULTS]\n            )\n        ).where(\n            and_(\n                symmetric_fkeys.columns[LEFT_REL] == search_fkey_graph.columns[RIGHT_REL],\n                search_fkey_graph.columns[DEPTH] < max_depth,\n                search_fkey_graph.columns[JP_PATH][-1] != jba(\n                    jba(\n                        symmetric_fkeys.columns[RIGHT_REL],\n                        symmetric_fkeys.columns[RIGHT_COL]\n                    ),\n                    jba(\n                        symmetric_fkeys.columns[LEFT_REL],\n                        symmetric_fkeys.columns[LEFT_COL]\n                    ),\n                )\n            )\n        )\n    )\n\n    output_cte = select(\n        cast(search_fkey_graph.columns[JP_PATH][0][0][0], Integer).label(BASE),\n        cast(search_fkey_graph.columns[JP_PATH][-1][-1][0], Integer).label(TARGET),\n        search_fkey_graph.columns[JP_PATH].label(JP_PATH),\n        search_fkey_graph.columns[FK_PATH].label(FK_PATH),\n        search_fkey_graph.columns[DEPTH].label(DEPTH),\n        search_fkey_graph.columns[MULTIPLE_RESULTS].label(MULTIPLE_RESULTS)\n    ).cte(name=OUTPUT_CTE)\n\n    if base_table_oid is not None:\n        final_sel = select(output_cte).where(\n            output_cte.columns[BASE] == base_table_oid\n        )\n    else:\n        final_sel = select(output_cte)\n\n    with engine.begin() as conn:\n        results = conn.execute(final_sel.limit(limit).offset(offset)).fetchall()\n\n    return results\n", "path": "db/tables/operations/select.py"}]}