|
1 | 1 | import pytest |
2 | | -import sqlite_utils |
| 2 | + |
| 3 | +from datasette.utils import escape_sqlite |
3 | 4 |
|
4 | 5 |
|
5 | 6 | # ensure refresh_schemas() gets called before interacting with internal_db |
@@ -76,19 +77,71 @@ async def test_internal_foreign_key_references(ds_client): |
76 | 77 | internal_db = await ensure_internal(ds_client) |
77 | 78 |
|
78 | 79 | def inner(conn): |
79 | | - db = sqlite_utils.Database(conn) |
80 | | - table_names = db.table_names() |
81 | | - for table in db.tables: |
82 | | - for fk in table.foreign_keys: |
83 | | - other_table = fk.other_table |
84 | | - other_column = fk.other_column |
85 | | - message = 'Column "{}.{}" references other column "{}.{}" which does not exist'.format( |
86 | | - table.name, fk.column, other_table, other_column |
| 80 | + table_names = [ |
| 81 | + row[0] |
| 82 | + for row in conn.execute( |
| 83 | + "select name from sqlite_master where type = 'table'" |
| 84 | + ).fetchall() |
| 85 | + ] |
| 86 | + |
| 87 | + def columns_for_table(table_name): |
| 88 | + return { |
| 89 | + row[1] |
| 90 | + for row in conn.execute( |
| 91 | + "PRAGMA table_info({})".format(escape_sqlite(table_name)) |
| 92 | + ).fetchall() |
| 93 | + } |
| 94 | + |
| 95 | + def primary_keys_for_table(table_name): |
| 96 | + return [ |
| 97 | + name |
| 98 | + for _, name in sorted( |
| 99 | + (row[5], row[1]) |
| 100 | + for row in conn.execute( |
| 101 | + "PRAGMA table_info({})".format(escape_sqlite(table_name)) |
| 102 | + ).fetchall() |
| 103 | + if row[5] |
| 104 | + ) |
| 105 | + ] |
| 106 | + |
| 107 | + columns_by_table = { |
| 108 | + table_name: columns_for_table(table_name) for table_name in table_names |
| 109 | + } |
| 110 | + |
| 111 | + for table_name in table_names: |
| 112 | + foreign_key_rows = conn.execute( |
| 113 | + "PRAGMA foreign_key_list({})".format(escape_sqlite(table_name)) |
| 114 | + ).fetchall() |
| 115 | + foreign_keys_by_id = {} |
| 116 | + for foreign_key in foreign_key_rows: |
| 117 | + foreign_keys_by_id.setdefault(foreign_key[0], []).append(foreign_key) |
| 118 | + |
| 119 | + for foreign_key_rows in foreign_keys_by_id.values(): |
| 120 | + foreign_key_rows.sort(key=lambda row: row[1]) |
| 121 | + other_table = foreign_key_rows[0][2] |
| 122 | + other_columns = [row[4] for row in foreign_key_rows] |
| 123 | + message = 'Column "{}.{}" references other table "{}" which does not exist'.format( |
| 124 | + table_name, foreign_key_rows[0][3], other_table |
87 | 125 | ) |
88 | 126 | assert other_table in table_names, message + " (bad table)" |
89 | | - assert other_column in db[other_table].columns_dict, ( |
90 | | - message + " (bad column)" |
| 127 | + if all(other_column is None for other_column in other_columns): |
| 128 | + other_columns = primary_keys_for_table(other_table) |
| 129 | + length_message = 'Foreign key from "{}" to "{}" has {} columns but references {} columns'.format( |
| 130 | + table_name, |
| 131 | + other_table, |
| 132 | + len(foreign_key_rows), |
| 133 | + len(other_columns), |
91 | 134 | ) |
| 135 | + assert len(other_columns) == len(foreign_key_rows), length_message |
| 136 | + |
| 137 | + for foreign_key, other_column in zip(foreign_key_rows, other_columns): |
| 138 | + column = foreign_key[3] |
| 139 | + message = 'Column "{}.{}" references other column "{}.{}" which does not exist'.format( |
| 140 | + table_name, column, other_table, other_column |
| 141 | + ) |
| 142 | + assert other_column in columns_by_table[other_table], ( |
| 143 | + message + " (bad column)" |
| 144 | + ) |
92 | 145 |
|
93 | 146 | await internal_db.execute_fn(inner) |
94 | 147 |
|
|
0 commit comments