diff --git a/wrolpi/db_query.py b/wrolpi/db_query.py index bf050f02..cc226456 100644 --- a/wrolpi/db_query.py +++ b/wrolpi/db_query.py @@ -157,7 +157,8 @@ def _is_fts_shadow_table(name: str, virtual_tables: List[str]) -> bool: def get_schema() -> List[dict]: - """Describe every table and view (but not SQLite internals or FTS5's shadow tables).""" + """Describe every table and view (but not SQLite internals or FTS5's shadow tables), with the foreign key each + column references.""" with read_only_connection() as conn: objects = conn.execute( "SELECT name, type, sql FROM sqlite_master" @@ -169,8 +170,14 @@ def get_schema() -> List[dict]: if _is_fts_shadow_table(name, virtual_tables): continue is_virtual = name in virtual_tables + # How this table joins to others, e.g. {'channel_id': 'channel.id'}. + references = { + from_: f'{table}.{to}' for _, _, table, from_, to, *_ in conn.execute( + 'SELECT * FROM pragma_foreign_key_list(?)', (name,)).fetchall() + } columns = [ - dict(name=col_name, type=col_type, not_null=bool(not_null), primary_key=bool(pk)) + dict(name=col_name, type=col_type, not_null=bool(not_null), primary_key=bool(pk), + references=references.get(col_name)) for _, col_name, col_type, not_null, _, pk in conn.execute( 'SELECT * FROM pragma_table_info(?)', (name,)).fetchall() ] diff --git a/wrolpi/test/test_db_query.py b/wrolpi/test/test_db_query.py index 0dd59880..faab16be 100644 --- a/wrolpi/test/test_db_query.py +++ b/wrolpi/test/test_db_query.py @@ -186,6 +186,12 @@ async def test_database_schema(test_session, async_client, allow_database_query) assert 'name' in columns assert any(i['columns'] == ['name'] for i in tag['indexes']) + # Foreign keys say how tables join; `video.channel_id` is a channel, not a collection. + video_columns = {i['name']: i for i in tables['video']['columns']} + assert video_columns['channel_id']['references'] == 'channel.id' + assert video_columns['file_group_id']['references'] == 'file_group.id' + assert columns['name']['references'] is None + # FTS5 tables are described (with their definition), but their shadow tables are not. virtual = [i for i in tables.values() if i['type'] == 'virtual'] assert virtual and all('fts5' in i['sql'].lower() for i in virtual)