Skip to content

Commit b1ef634

Browse files
fix: make table name quoting backend-agnostic
- Update get_master() regex to match both MySQL backticks and PostgreSQL double quotes - Use adapter.quote_identifier() for FreeTable construction in schemas.py - Add pattern parameter to list_tables_sql() for job table queries - Use list_tables_sql() instead of hardcoded SHOW TABLES in jobs property - Update FreeTable.__repr__ to use full_table_name property Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent f113f92 commit b1ef634

File tree

6 files changed

+30
-12
lines changed

6 files changed

+30
-12
lines changed

src/datajoint/adapters/base.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,14 +598,16 @@ def list_schemas_sql(self) -> str:
598598
...
599599

600600
@abstractmethod
601-
def list_tables_sql(self, schema_name: str) -> str:
601+
def list_tables_sql(self, schema_name: str, pattern: str | None = None) -> str:
602602
"""
603603
Generate query to list tables in a schema.
604604
605605
Parameters
606606
----------
607607
schema_name : str
608608
Name of schema to list tables from.
609+
pattern : str, optional
610+
LIKE pattern to filter table names. Use %% for % in SQL.
609611
610612
Returns
611613
-------

src/datajoint/adapters/mysql.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -606,9 +606,12 @@ def list_schemas_sql(self) -> str:
606606
"""Query to list all databases in MySQL."""
607607
return "SELECT schema_name FROM information_schema.schemata"
608608

609-
def list_tables_sql(self, schema_name: str) -> str:
609+
def list_tables_sql(self, schema_name: str, pattern: str | None = None) -> str:
610610
"""Query to list tables in a database."""
611-
return f"SHOW TABLES IN {self.quote_identifier(schema_name)}"
611+
sql = f"SHOW TABLES IN {self.quote_identifier(schema_name)}"
612+
if pattern:
613+
sql += f" LIKE '{pattern}'"
614+
return sql
612615

613616
def get_table_info_sql(self, schema_name: str, table_name: str) -> str:
614617
"""Query to get table metadata (comment, engine, etc.)."""

src/datajoint/adapters/postgres.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -684,13 +684,16 @@ def list_schemas_sql(self) -> str:
684684
"WHERE schema_name NOT IN ('pg_catalog', 'information_schema')"
685685
)
686686

687-
def list_tables_sql(self, schema_name: str) -> str:
687+
def list_tables_sql(self, schema_name: str, pattern: str | None = None) -> str:
688688
"""Query to list tables in a schema."""
689-
return (
689+
sql = (
690690
f"SELECT table_name FROM information_schema.tables "
691691
f"WHERE table_schema = {self.quote_string(schema_name)} "
692692
f"AND table_type = 'BASE TABLE'"
693693
)
694+
if pattern:
695+
sql += f" AND table_name LIKE '{pattern}'"
696+
return sql
694697

695698
def get_table_info_sql(self, schema_name: str, table_name: str) -> str:
696699
"""Query to get table metadata."""

src/datajoint/dependencies.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,14 @@ def extract_master(part_table: str) -> str | None:
3131
str or None
3232
Master table name if part_table is a part table, None otherwise.
3333
"""
34-
match = re.match(r"(?P<master>`\w+`.`#?\w+)__\w+`", part_table)
35-
return match["master"] + "`" if match else None
34+
# Match both MySQL backticks and PostgreSQL double quotes
35+
# MySQL: `schema`.`master__part`
36+
# PostgreSQL: "schema"."master__part"
37+
match = re.match(r'(?P<master>(?P<q>[`"])[\w]+(?P=q)\.(?P=q)#?[\w]+)__[\w]+(?P=q)', part_table)
38+
if match:
39+
q = match["q"]
40+
return match["master"] + q
41+
return None
3642

3743

3844
def topo_sort(graph: nx.DiGraph) -> list[str]:

src/datajoint/schemas.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -517,13 +517,16 @@ def jobs(self) -> list[Job]:
517517
jobs_list = []
518518

519519
# Get all existing job tables (~~prefix)
520-
# Note: %% escapes the % in pymysql
521-
result = self.connection.query(f"SHOW TABLES IN `{self.database}` LIKE '~~%%'").fetchall()
520+
# Note: %% escapes the % in pymysql/psycopg2
521+
adapter = self.connection.adapter
522+
sql = adapter.list_tables_sql(self.database, pattern="~~%%")
523+
result = self.connection.query(sql).fetchall()
522524
existing_job_tables = {row[0] for row in result}
523525

524526
# Iterate over auto-populated tables and check if their job table exists
525527
for table_name in self.list_tables():
526-
table = FreeTable(self.connection, f"`{self.database}`.`{table_name}`")
528+
adapter = self.connection.adapter
529+
table = FreeTable(self.connection, f"{adapter.quote_identifier(self.database)}.{adapter.quote_identifier(table_name)}")
527530
tier = _get_tier(table.full_table_name)
528531
if tier in (Computed, Imported):
529532
# Compute expected job table name: ~~base_name
@@ -696,7 +699,8 @@ def get_table(self, name: str) -> FreeTable:
696699
if table_name is None:
697700
raise DataJointError(f"Table `{name}` does not exist in schema `{self.database}`.")
698701

699-
full_name = f"`{self.database}`.`{table_name}`"
702+
adapter = self.connection.adapter
703+
full_name = f"{adapter.quote_identifier(self.database)}.{adapter.quote_identifier(table_name)}"
700704
return FreeTable(self.connection, full_name)
701705

702706
def __getitem__(self, name: str) -> FreeTable:

src/datajoint/table.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1443,4 +1443,4 @@ def __init__(self, conn, full_table_name):
14431443
)
14441444

14451445
def __repr__(self):
1446-
return "FreeTable(`%s`.`%s`)\n" % (self.database, self._table_name) + super().__repr__()
1446+
return f"FreeTable({self.full_table_name})\n" + super().__repr__()

0 commit comments

Comments
 (0)