@@ -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