From c4f0365130a30a674f64c06fbe7ff5dfbfd3d041 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Thu, 23 Oct 2025 15:29:51 -0700 Subject: [PATCH] Rewrite tables endpoint to use SQL LIKE instead of Python regex --- datasette/views/special.py | 58 ++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/datasette/views/special.py b/datasette/views/special.py index 97396927..fcef3c42 100644 --- a/datasette/views/special.py +++ b/datasette/views/special.py @@ -948,31 +948,41 @@ class TablesView(BaseView): if not q: return Response.json({"matches": []}) - # Use the new allowed_resources() method - tables = await self.ds.allowed_resources("view-table", request.actor) - - # Convert to list of matches with name and url - matches = [ - { - "name": f"{table.parent}: {table.child}", - "url": self.ds.urls.table(table.parent, table.child), - } - for table in tables - ] - - # Apply search filter - import re - - # Split search terms by whitespace + # Build SQL LIKE pattern from search terms + # Split search terms by whitespace and build pattern: %term1%term2%term3% terms = q.split() - # Build regex pattern: .*term1.*term2.*term3.* - pattern = ".*" + ".*".join(re.escape(term) for term in terms) + ".*" - regex = re.compile(pattern, re.IGNORECASE) + pattern = "%" + "%".join(terms) + "%" - # Filter tables matching the pattern (extract table name from "db: table") - matches = [m for m in matches if regex.match(m["name"].split(": ", 1)[1])] - - # Sort by shortest table name first - matches.sort(key=lambda m: len(m["name"].split(": ", 1)[1])) + matches = [] + # Query each database's sqlite_master for matching tables + for db_name in self.ds.databases.keys(): + db = self.ds.databases[db_name] + try: + # Use SQL to find matching table names, ordered by shortest first + result = await db.execute( + """ + SELECT name + FROM sqlite_master + WHERE type='table' + AND name LIKE ? COLLATE NOCASE + ORDER BY length(name), name + """, + [pattern] + ) + # Check permissions for each matching table + for row in result.rows: + table_name = row[0] + if await self.ds.permission_allowed( + request.actor, + "view-table", + resource=(db_name, table_name) + ): + matches.append({ + "name": f"{db_name}: {table_name}", + "url": self.ds.urls.table(db_name, table_name), + }) + except Exception: + # Skip databases that can't be queried + continue return Response.json({"matches": matches})