Database schema lists each column's foreign key (#822)
Some checks are pending
Backend Tests / api-tests (core) (push) Waiting to run
Backend Tests / api-tests (files) (push) Waiting to run
Backend Tests / api-tests (media) (push) Waiting to run
Backend Tests / api-tests (rest) (push) Waiting to run
Backend Tests / api-contract-tests (push) Waiting to run
Backend Tests / api-migrations-and-start (push) Waiting to run
CodeQL / Analyze (push) Waiting to run
Controller Tests / controller-tests (push) Waiting to run
Cypress Tests / cypress-component-tests (push) Waiting to run
Cypress Tests / cypress-e2e-tests (push) Waiting to run
Jest Tests / jest-tests (push) Waiting to run

Without them a model guessed joins: it joined video.channel_id to collection.id
(it is channel.id), and the query silently returned the wrong channel names.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
lrnselfreliance 2026-10-11 19:38:29 -06:00 • committed by Roland
parent 61e58fac17
commit 87e146487b
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
2 changed files with 15 additions and 2 deletions

View file

@ -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()
]

View file

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