Skip to content

Commit 0469a72

Browse files
fix: use PostgreSQL-specific queries for dependencies loading
PostgreSQL's information_schema doesn't have MySQL-specific columns (referenced_table_schema, referenced_table_name, referenced_column_name). Use backend-specific queries: - MySQL: Direct query with referenced_* columns - PostgreSQL: JOIN with referential_constraints and constraint_column_usage Also fix primary key constraint detection: - MySQL: constraint_name='PRIMARY' - PostgreSQL: constraint_type='PRIMARY KEY' Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 6b2b7e4 commit 0469a72

File tree

1 file changed

+77
-37
lines changed

1 file changed

+77
-37
lines changed

src/datajoint/dependencies.py

Lines changed: 77 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -157,53 +157,93 @@ def load(self, force: bool = True) -> None:
157157
# Build schema list for IN clause
158158
schemas_list = ", ".join(adapter.quote_string(s) for s in self._conn.schemas)
159159

160-
# Backend-specific table name concatenation
161-
# MySQL: concat('`', table_schema, '`.`', table_name, '`')
162-
# PostgreSQL: '"' || table_schema || '"."' || table_name || '"'
163-
# Note: MySQL uses %% to escape % in LIKE patterns (PyMySQL format strings)
160+
# Backend-specific queries for primary keys and foreign keys
161+
# Note: Both PyMySQL and psycopg2 use %s placeholders, so escape % as %%
162+
like_pattern = "'~%%'"
163+
164164
if adapter.backend == "mysql":
165+
# MySQL: use concat() and MySQL-specific information_schema columns
165166
tab_expr = "concat('`', table_schema, '`.`', table_name, '`')"
167+
168+
# load primary key info (MySQL uses constraint_name='PRIMARY')
169+
keys = self._conn.query(
170+
f"""
171+
SELECT {tab_expr} as tab, column_name
172+
FROM information_schema.key_column_usage
173+
WHERE table_name NOT LIKE {like_pattern}
174+
AND table_schema in ({schemas_list})
175+
AND constraint_name='PRIMARY'
176+
"""
177+
)
178+
pks = defaultdict(set)
179+
for key in keys:
180+
pks[key[0]].add(key[1])
181+
182+
# load foreign keys (MySQL has referenced_* columns)
166183
ref_tab_expr = "concat('`', referenced_table_schema, '`.`', referenced_table_name, '`')"
167-
like_pattern = "'~%%'" # Double %% for PyMySQL escaping
168-
else:
169-
# PostgreSQL
170-
tab_expr = "'\"' || table_schema || '\".\"' || table_name || '\"'"
171-
ref_tab_expr = "'\"' || referenced_table_schema || '\".\"' || referenced_table_name || '\"'"
172-
like_pattern = "'~%%'" # psycopg2 also uses %s placeholders, so escape %
173-
174-
# load primary key info
175-
keys = self._conn.query(
176-
f"""
177-
SELECT
178-
{tab_expr} as tab, column_name
184+
fk_keys = self._conn.query(
185+
f"""
186+
SELECT constraint_name,
187+
{tab_expr} as referencing_table,
188+
{ref_tab_expr} as referenced_table,
189+
column_name, referenced_column_name
179190
FROM information_schema.key_column_usage
180-
WHERE table_name NOT LIKE {like_pattern} AND table_schema in ({schemas_list}) AND constraint_name='PRIMARY'
191+
WHERE referenced_table_name NOT LIKE {like_pattern}
192+
AND (referenced_table_schema in ({schemas_list})
193+
OR referenced_table_schema is not NULL AND table_schema in ({schemas_list}))
194+
""",
195+
as_dict=True,
196+
)
197+
else:
198+
# PostgreSQL: use || concatenation and different query structure
199+
tab_expr = "'\"' || kcu.table_schema || '\".\"' || kcu.table_name || '\"'"
200+
201+
# load primary key info (PostgreSQL uses constraint_type='PRIMARY KEY')
202+
keys = self._conn.query(
203+
f"""
204+
SELECT {tab_expr} as tab, kcu.column_name
205+
FROM information_schema.key_column_usage kcu
206+
JOIN information_schema.table_constraints tc
207+
ON kcu.constraint_name = tc.constraint_name
208+
AND kcu.table_schema = tc.table_schema
209+
WHERE kcu.table_name NOT LIKE {like_pattern}
210+
AND kcu.table_schema in ({schemas_list})
211+
AND tc.constraint_type = 'PRIMARY KEY'
181212
"""
182-
)
183-
pks = defaultdict(set)
184-
for key in keys:
185-
pks[key[0]].add(key[1])
213+
)
214+
pks = defaultdict(set)
215+
for key in keys:
216+
pks[key[0]].add(key[1])
217+
218+
# load foreign keys (PostgreSQL requires joining multiple tables)
219+
ref_tab_expr = "'\"' || ccu.table_schema || '\".\"' || ccu.table_name || '\"'"
220+
fk_keys = self._conn.query(
221+
f"""
222+
SELECT kcu.constraint_name,
223+
{tab_expr} as referencing_table,
224+
{ref_tab_expr} as referenced_table,
225+
kcu.column_name, ccu.column_name as referenced_column_name
226+
FROM information_schema.key_column_usage kcu
227+
JOIN information_schema.referential_constraints rc
228+
ON kcu.constraint_name = rc.constraint_name
229+
AND kcu.constraint_schema = rc.constraint_schema
230+
JOIN information_schema.constraint_column_usage ccu
231+
ON rc.unique_constraint_name = ccu.constraint_name
232+
AND rc.unique_constraint_schema = ccu.constraint_schema
233+
WHERE kcu.table_name NOT LIKE {like_pattern}
234+
AND (ccu.table_schema in ({schemas_list})
235+
OR kcu.table_schema in ({schemas_list}))
236+
ORDER BY kcu.constraint_name, kcu.ordinal_position
237+
""",
238+
as_dict=True,
239+
)
186240

187241
# add nodes to the graph
188242
for n, pk in pks.items():
189243
self.add_node(n, primary_key=pk)
190244

191-
# load foreign keys
192-
keys = (
193-
{k.lower(): v for k, v in elem.items()}
194-
for elem in self._conn.query(
195-
f"""
196-
SELECT constraint_name,
197-
{tab_expr} as referencing_table,
198-
{ref_tab_expr} as referenced_table,
199-
column_name, referenced_column_name
200-
FROM information_schema.key_column_usage
201-
WHERE referenced_table_name NOT LIKE {like_pattern} AND (referenced_table_schema in ({schemas_list}) OR
202-
referenced_table_schema is not NULL AND table_schema in ({schemas_list}))
203-
""",
204-
as_dict=True,
205-
)
206-
)
245+
# Process foreign keys (same for both backends)
246+
keys = ({k.lower(): v for k, v in elem.items()} for elem in fk_keys)
207247
fks = defaultdict(lambda: dict(attr_map=dict()))
208248
for key in keys:
209249
d = fks[

0 commit comments

Comments
 (0)