\\w+))?$\",\r\n )\r\n add_route(\r\n TableInsertView.as_view(self),\r\n@@ -1440,7 +1440,15 @@ class Datasette:\r\n async def resolve_row(self, request):\r\n db, table_name, _ = await self.resolve_table(request)\r\n pk_values = urlsafe_components(request.url_vars[\"pks\"])\r\n- sql, params, pks = await row_sql_params_pks(db, table_name, pk_values)\r\n+\r\n+ if len(pk_values) == 1 and pk_values[0].startswith(\".\"):\r\n+ # It's a special .rowid value\r\n+ pk_values = (pk_values[0][1:],)\r\n+ sql, params, pks = await row_sql_params_pks(\r\n+ db, table_name, pk_values, rowid=True\r\n+ )\r\n+ else:\r\n+ sql, params, pks = await row_sql_params_pks(db, table_name, pk_values)\r\n results = await db.execute(sql, params, truncate=True)\r\n row = results.first()\r\n if row is None:\r\ndiff --git a/datasette/utils/__init__.py b/datasette/utils/__init__.py\r\nindex c388673d..96669281 100644\r\n--- a/datasette/utils/__init__.py\r\n+++ b/datasette/utils/__init__.py\r\n@@ -1206,9 +1206,12 @@ def truncate_url(url, length):\r\n return url[: length - 1] + \"\u2026\"\r\n \r\n \r\n-async def row_sql_params_pks(db, table, pk_values):\r\n+async def row_sql_params_pks(db, table, pk_values, rowid=False):\r\n pks = await db.primary_keys(table)\r\n- use_rowid = not pks\r\n+ if rowid:\r\n+ use_rowid = True\r\n+ else:\r\n+ use_rowid = not pks\r\n select = \"*\"\r\n if use_rowid:\r\n select = \"rowid, *\"\r\n```\r\nIt works:\r\n\r\n\r\n\r\n\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1857234285, "label": "If a row has a primary key of `null` various things break"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/datasette/issues/2145#issuecomment-1684525054", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2145", "id": 1684525054, "node_id": "IC_kwDOBm6k_c5kZ8_-", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T23:02:26Z", "updated_at": "2023-08-18T23:02:26Z", "author_association": "OWNER", "body": "Creating a quick test database:\r\n```bash\r\nsqlite-utils create-table nulls.db nasty id text --pk id\r\nsqlite-utils nulls.db 'insert into nasty (id) values (null)'\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1857234285, "label": "If a row has a primary key of `null` various things break"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/datasette/issues/2145#issuecomment-1684523322", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2145", "id": 1684523322, "node_id": "IC_kwDOBm6k_c5kZ8k6", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T22:59:14Z", "updated_at": "2023-08-18T22:59:14Z", "author_association": "OWNER", "body": "Except it looks like the Links from other tables section is broken:\r\n\r\n\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1857234285, "label": "If a row has a primary key of `null` various things break"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/datasette/issues/2145#issuecomment-1684525943", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2145", "id": 1684525943, "node_id": "IC_kwDOBm6k_c5kZ9N3", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T23:04:14Z", "updated_at": "2023-08-18T23:04:14Z", "author_association": "OWNER", "body": "This is hard. I tried this:\r\n```python\r\ndef path_from_row_pks(row, pks, use_rowid, quote=True):\r\n \"\"\"Generate an optionally tilde-encoded unique identifier\r\n for a row from its primary keys.\"\"\"\r\n if use_rowid or any(row[pk] is None for pk in pks):\r\n bits = [row[\"rowid\"]]\r\n else:\r\n bits = [\r\n row[pk][\"value\"] if isinstance(row[pk], dict) else row[pk] for pk in pks\r\n ]\r\n if quote:\r\n bits = [tilde_encode(str(bit)) for bit in bits]\r\n else:\r\n bits = [str(bit) for bit in bits]\r\n\r\n return \",\".join(bits)\r\n```\r\nThe ` if use_rowid or any(row[pk] is None for pk in pks)` bit is new.\r\n\r\nBut I got this error on http://127.0.0.1:8003/nulls/nasty :\r\n\r\n```\r\n File \"/Users/simon/Dropbox/Development/datasette/datasette/views/table.py\", line 1364, in run_display_columns_and_rows\r\n display_columns, display_rows = await display_columns_and_rows(\r\n File \"/Users/simon/Dropbox/Development/datasette/datasette/views/table.py\", line 186, in display_columns_and_rows\r\n pk_path = path_from_row_pks(row, pks, not pks, False)\r\n File \"/Users/simon/Dropbox/Development/datasette/datasette/utils/__init__.py\", line 124, in path_from_row_pks\r\n bits = [row[\"rowid\"]]\r\nIndexError: No item with that key\r\n```\r\nBecause the SQL query I ran to populate the page didn't know that it would need to select `rowid` as well.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1857234285, "label": "If a row has a primary key of `null` various things break"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/datasette/issues/2145#issuecomment-1684526447", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2145", "id": 1684526447, "node_id": "IC_kwDOBm6k_c5kZ9Vv", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T23:05:02Z", "updated_at": "2023-08-18T23:05:02Z", "author_association": "OWNER", "body": "How expensive is it to detect if a SQLite table contains at least one `null` primary key?", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1857234285, "label": "If a row has a primary key of `null` various things break"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/datasette/issues/2145#issuecomment-1684384750", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2145", "id": 1684384750, "node_id": "IC_kwDOBm6k_c5kZavu", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T20:07:18Z", "updated_at": "2023-08-18T20:07:18Z", "author_association": "OWNER", "body": "The big challenge here is what the URL to that row page should look like. How can I encode a `None` in a form that can be encoded and decoded without clashing with primary keys that are the string `None` or `null`?", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1857234285, "label": "If a row has a primary key of `null` various things break"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/datasette/issues/2143#issuecomment-1684484426", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2143", "id": 1684484426, "node_id": "IC_kwDOBm6k_c5kZzFK", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T22:12:52Z", "updated_at": "2023-08-18T22:12:52Z", "author_association": "OWNER", "body": "Yeah, I'm convinced by that. There's not point in having both `settings.json` and `datasette.json`.\r\n\r\nI like `datasette.json` ( / `datasette.yml`) as a name. That can be the file that lives in your config directory too, so if you run `datasette .` in a folder containing `datasette.yml` all of those settings get picked up.\r\n\r\nHere's a thought for how it could look - I'll go with the YAML format because I expect that to be the default most people use, just because it supports multi-line strings better.\r\n\r\nI based this on the big example at https://docs.datasette.io/en/1.0a3/metadata.html#using-yaml-for-metadata - and combined some bits from https://docs.datasette.io/en/1.0a3/authentication.html as well.\r\n\r\n```yaml\r\ntitle: Demonstrating Metadata from YAML\r\ndescription_html: |-\r\n This description includes a long HTML string
\r\n \r\n - YAML is better for embedding HTML strings than JSON!
\r\n
\r\n\r\nsettings:\r\n default_page_size: 10\r\n max_returned_rows: 3000\r\n sql_time_limit_ms\": 8000\r\n\r\ndatabases:\r\n docs:\r\n permissions:\r\n create-table:\r\n id: editor\r\n fixtures:\r\n tables:\r\n no_primary_key:\r\n hidden: true\r\n queries:\r\n neighborhood_search:\r\n sql: |-\r\n select neighborhood, facet_cities.name, state\r\n from facetable join facet_cities on facetable.city_id = facet_cities.id\r\n where neighborhood like '%' || :text || '%' order by neighborhood;\r\n title: Search neighborhoods\r\n description_html: |-\r\n This demonstrates basic LIKE search\r\n\r\npermissions:\r\n debug-menu:\r\n id: '*'\r\n\r\nplugins:\r\n datasette-ripgrep:\r\n path: /usr/local/lib/python3.11/site-packages\r\n```\r\nI'm inclined to say we try to be a super-set of the existing `metadata.yml` format, at least where it makes sense to do so. That way the upgrade path is smooth for people. Also, I don't think the format itself is terrible - it's the name that's the big problem.\r\n\r\nIn this example I've mixed in one extra concept: that `settings:` block with a bunch of settings in it.\r\n\r\nThere are some things in there that look a little bit like metadata - the `title` and `description_html` fields.\r\n\r\nBut _are they_ metadata? The title and description of the overall instance feels like it could be described as general configuration. The stuff for the `query` should live where the query itself is defined.\r\n\r\nNote that queries can be defined by a plugin hook too: https://docs.datasette.io/en/1.0a3/plugin_hooks.html#canned-queries-datasette-database-actor\r\n\r\nWhat do you think? Is this the right direction, or are you thinking there's a more radical redesign that would make sense here?", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1855885427, "label": "De-tangling Metadata before Datasette 1.0"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/datasette/issues/2143#issuecomment-1684485591", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2143", "id": 1684485591, "node_id": "IC_kwDOBm6k_c5kZzXX", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T22:14:35Z", "updated_at": "2023-08-18T22:14:35Z", "author_association": "OWNER", "body": "Actually there is one thing that I'm not comfortable about with respect to the existing design: the way the database / tables stuff is nested.\r\n\r\nThey assume that the user will attach the database to Datasette using a fixed name - `docs.db` or whatever.\r\n\r\nBut what if we want to support users downloading databases from each other and attaching them to Datasette where those DBs might carry some of their own configuration?\r\n\r\nMoving metadata into the databases makes sense there, but what about database-specific settings like the default sort order for a table, or configured canned queries?\r\n\r\nHaving those tied to the filename of the database itself feels unpleasant to me. But how else could we handle this?", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1855885427, "label": "De-tangling Metadata before Datasette 1.0"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/sqlite-utils/issues/585#issuecomment-1683197882", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/585", "id": 1683197882, "node_id": "IC_kwDOCGYnMM5kU4-6", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T01:25:53Z", "updated_at": "2023-08-18T01:25:53Z", "author_association": "OWNER", "body": "Probably most relevant here is this snippet from:\r\n```bash\r\nsqlite-utils create-table --help\r\n```\r\n```\r\n --default ... Default value that should be set for a column\r\n --fk ... Column, other table, other column to set as a\r\n foreign key\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1855894222, "label": "CLI equivalents to `transform(add_foreign_keys=)`"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/sqlite-utils/issues/585#issuecomment-1683195669", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/585", "id": 1683195669, "node_id": "IC_kwDOCGYnMM5kU4cV", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T01:24:57Z", "updated_at": "2023-08-18T01:24:57Z", "author_association": "OWNER", "body": "Currently:\r\n```bash\r\nsqlite-utils transform --help\r\n```\r\n```\r\nUsage: sqlite-utils transform [OPTIONS] PATH TABLE\r\n\r\n Transform a table beyond the capabilities of ALTER TABLE\r\n\r\n Example:\r\n\r\n sqlite-utils transform mydb.db mytable \\\r\n --drop column1 \\\r\n --rename column2 column_renamed\r\n\r\nOptions:\r\n --type ... Change column type to INTEGER, TEXT, FLOAT or BLOB\r\n --drop TEXT Drop this column\r\n --rename ... Rename this column to X\r\n -o, --column-order TEXT Reorder columns\r\n --not-null TEXT Set this column to NOT NULL\r\n --not-null-false TEXT Remove NOT NULL from this column\r\n --pk TEXT Make this column the primary key\r\n --pk-none Remove primary key (convert to rowid table)\r\n --default ... Set default value for this column\r\n --default-none TEXT Remove default from this column\r\n --drop-foreign-key TEXT Drop foreign key constraint for this column\r\n --sql Output SQL without executing it\r\n --load-extension TEXT Path to SQLite extension, with optional\r\n :entrypoint\r\n -h, --help Show this message and exit.\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1855894222, "label": "CLI equivalents to `transform(add_foreign_keys=)`"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/sqlite-utils/issues/585#issuecomment-1683198740", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/585", "id": 1683198740, "node_id": "IC_kwDOCGYnMM5kU5MU", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T01:26:47Z", "updated_at": "2023-08-18T01:26:47Z", "author_association": "OWNER", "body": "The only CLI feature that supports providing just the column name appears to be this:\r\n```bash\r\nsqlite-utils add-foreign-key --help\r\n```\r\n```\r\nUsage: sqlite-utils add-foreign-key [OPTIONS] PATH TABLE COLUMN [OTHER_TABLE]\r\n [OTHER_COLUMN]\r\n\r\n Add a new foreign key constraint to an existing table\r\n\r\n Example:\r\n\r\n sqlite-utils add-foreign-key my.db books author_id authors id\r\n\r\n WARNING: Could corrupt your database! Back up your database file first.\r\n```\r\nI can drop that WARNING now since I'm not writing to `sqlite_master` any more.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1855894222, "label": "CLI equivalents to `transform(add_foreign_keys=)`"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/sqlite-utils/issues/585#issuecomment-1683200128", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/585", "id": 1683200128, "node_id": "IC_kwDOCGYnMM5kU5iA", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T01:29:00Z", "updated_at": "2023-08-18T01:29:00Z", "author_association": "OWNER", "body": "I'm not going to implement the `foreign_keys=` option that entirely replaces existing foreign keys - I'll just do a `--add-foreign-key` multi-option.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1855894222, "label": "CLI equivalents to `transform(add_foreign_keys=)`"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/sqlite-utils/issues/585#issuecomment-1683201239", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/585", "id": 1683201239, "node_id": "IC_kwDOCGYnMM5kU5zX", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T01:30:46Z", "updated_at": "2023-08-18T01:30:46Z", "author_association": "OWNER", "body": "Help can now look like this:\r\n```\r\n --drop-foreign-key TEXT Drop foreign key constraint for this column\r\n --add-foreign-key ...\r\n Add a foreign key constraint from a column\r\n to another table with another column\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1855894222, "label": "CLI equivalents to `transform(add_foreign_keys=)`"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/sqlite-utils/issues/585#issuecomment-1683212074", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/585", "id": 1683212074, "node_id": "IC_kwDOCGYnMM5kU8cq", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T01:43:54Z", "updated_at": "2023-08-18T01:43:54Z", "author_association": "OWNER", "body": "Some manual testing:\r\n```bash\r\nsqlite-utils create-table /tmp/t.db places id integer name text country integer city integer continent integer --pk id\r\nsqlite-utils schema /tmp/t.db\r\n```\r\n```sql\r\nCREATE TABLE [places] (\r\n [id] INTEGER PRIMARY KEY,\r\n [name] TEXT,\r\n [country] INTEGER,\r\n [city] INTEGER,\r\n [continent] INTEGER\r\n);\r\n```\r\n```bash\r\nsqlite-utils create-table /tmp/t.db country id integer name text\r\nsqlite-utils create-table /tmp/t.db city id integer name text\r\nsqlite-utils create-table /tmp/t.db continent id integer name text\r\nsqlite-utils schema /tmp/t.db\r\n```\r\n```sql\r\nCREATE TABLE [places] (\r\n [id] INTEGER PRIMARY KEY,\r\n [name] TEXT,\r\n [country] INTEGER,\r\n [city] INTEGER,\r\n [continent] INTEGER\r\n);\r\nCREATE TABLE [country] (\r\n [id] INTEGER,\r\n [name] TEXT\r\n);\r\nCREATE TABLE [city] (\r\n [id] INTEGER,\r\n [name] TEXT\r\n);\r\nCREATE TABLE [continent] (\r\n [id] INTEGER,\r\n [name] TEXT\r\n);\r\n```\r\n```bash\r\nsqlite-utils transform /tmp/t.db places --add-foreign-key country country id --add-foreign-key continent continent id\r\nsqlite-utils schema /tmp/t.db\r\n```\r\n```sql\r\nCREATE TABLE [country] (\r\n [id] INTEGER,\r\n [name] TEXT\r\n);\r\nCREATE TABLE [city] (\r\n [id] INTEGER,\r\n [name] TEXT\r\n);\r\nCREATE TABLE [continent] (\r\n [id] INTEGER,\r\n [name] TEXT\r\n);\r\nCREATE TABLE \"places\" (\r\n [id] INTEGER PRIMARY KEY,\r\n [name] TEXT,\r\n [country] INTEGER REFERENCES [country]([id]),\r\n [city] INTEGER,\r\n [continent] INTEGER REFERENCES [continent]([id])\r\n);\r\n```\r\n```bash\r\nsqlite-utils transform /tmp/t.db places --drop-foreign-key country\r\nsqlite-utils schema /tmp/t.db places\r\n```\r\n```sql\r\nCREATE TABLE \"places\" (\r\n [id] INTEGER PRIMARY KEY,\r\n [name] TEXT,\r\n [country] INTEGER,\r\n [city] INTEGER,\r\n [continent] INTEGER REFERENCES [continent]([id])\r\n)\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1855894222, "label": "CLI equivalents to `transform(add_foreign_keys=)`"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/sqlite-utils/issues/585#issuecomment-1683217284", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/585", "id": 1683217284, "node_id": "IC_kwDOCGYnMM5kU9uE", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T01:50:21Z", "updated_at": "2023-08-18T01:50:21Z", "author_association": "OWNER", "body": "And a test of the `--sql` option:\r\n```bash\r\nsqlite-utils create-table /tmp/t.db places id integer name text country integer city integer continent integer --pk id\r\nsqlite-utils create-table /tmp/t.db country id integer name text\r\nsqlite-utils create-table /tmp/t.db city id integer name text\r\nsqlite-utils create-table /tmp/t.db continent id integer name text\r\nsqlite-utils transform /tmp/t.db places --add-foreign-key country country id --add-foreign-key continent continent id --sql\r\n```\r\nOutputs:\r\n```sql\r\nCREATE TABLE [places_new_6a705d2f5a13] (\r\n [id] INTEGER PRIMARY KEY,\r\n [name] TEXT,\r\n [country] INTEGER REFERENCES [country]([id]),\r\n [city] INTEGER,\r\n [continent] INTEGER REFERENCES [continent]([id])\r\n);\r\nINSERT INTO [places_new_6a705d2f5a13] ([id], [name], [country], [city], [continent])\r\n SELECT [id], [name], [country], [city], [continent] FROM [places];\r\nDROP TABLE [places];\r\nALTER TABLE [places_new_6a705d2f5a13] RENAME TO [places];\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1855894222, "label": "CLI equivalents to `transform(add_foreign_keys=)`"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/sqlite-utils/pull/584#issuecomment-1683137259", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/584", "id": 1683137259, "node_id": "IC_kwDOCGYnMM5kUqLr", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T00:06:59Z", "updated_at": "2023-08-18T00:06:59Z", "author_association": "OWNER", "body": "The docs still describe the old trick, I need to update that: https://sqlite-utils.datasette.io/en/3.34/python-api.html#adding-foreign-key-constraints", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1855838223, "label": ".transform() instead of modifying sqlite_master for add_foreign_keys"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/sqlite-utils/pull/584#issuecomment-1683138953", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/584", "id": 1683138953, "node_id": "IC_kwDOCGYnMM5kUqmJ", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T00:09:20Z", "updated_at": "2023-08-18T00:09:20Z", "author_association": "OWNER", "body": "Weird, I'm getting a `flake8` problem in CI which doesn't occur on my laptop:\r\n```\r\n./tests/test_recipes.py:99:9: F811 redefinition of unused 'fn' from line 96\r\n./tests/test_recipes.py:127:9: F811 redefinition of unused 'fn' from line 124\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1855838223, "label": ".transform() instead of modifying sqlite_master for add_foreign_keys"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/sqlite-utils/pull/584#issuecomment-1683139304", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/584", "id": 1683139304, "node_id": "IC_kwDOCGYnMM5kUqro", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T00:09:56Z", "updated_at": "2023-08-18T00:09:56Z", "author_association": "OWNER", "body": "Upgrading `flake8` locally replicated the error:\r\n```\r\npip install -U flake8\r\nflake8\r\n```\r\n```\r\n./tests/test_recipes.py:99:9: F811 redefinition of unused 'fn' from line 96\r\n./tests/test_recipes.py:127:9: F811 redefinition of unused 'fn' from line 124\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1855838223, "label": ".transform() instead of modifying sqlite_master for add_foreign_keys"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/sqlite-utils/pull/584#issuecomment-1683143723", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/584", "id": 1683143723, "node_id": "IC_kwDOCGYnMM5kUrwr", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T00:14:52Z", "updated_at": "2023-08-18T00:14:52Z", "author_association": "OWNER", "body": "Another docs update: this bit in here https://sqlite-utils.datasette.io/en/3.34/python-api.html#adding-multiple-foreign-key-constraints-at-once talks about how `.add_foreign_keys()` is a performance optimization to avoid having to run VACUUM a bunch of separate times:\r\n\r\n> The final step in adding a new foreign key to a SQLite database is to run `VACUUM`, to ensure the new foreign key is available in future introspection queries.\r\n> \r\n> `VACUUM` against a large (multi-GB) database can take several minutes or longer. If you are adding multiple foreign keys using `table.add_foreign_key(...)` these can quickly add up.\r\n> \r\n> Instead, you can use `db.add_foreign_keys(...)` to add multiple foreign keys within a single transaction. This method takes a list of four-tuples, each one specifying a `table`, `column`, `other_table` and `other_column`.\r\n\r\nThat doesn't apply any more - the new mechanism using `.transform()` works completely differently, so this issue around running VACUUM no longer applies.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1855838223, "label": ".transform() instead of modifying sqlite_master for add_foreign_keys"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/sqlite-utils/pull/584#issuecomment-1683145110", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/584", "id": 1683145110, "node_id": "IC_kwDOCGYnMM5kUsGW", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T00:16:28Z", "updated_at": "2023-08-18T00:16:48Z", "author_association": "OWNER", "body": "One last piece of documentation: need to document the new option to `table.transform()` and `table.transform_sql()`:\r\n\r\nhttps://github.com/simonw/sqlite-utils/blob/0771ac61fe5c2aca74075b20b1a99b9bd4c65661/sqlite_utils/db.py#L1706-L1708\r\n\r\nI should write tests for them too.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1855838223, "label": ".transform() instead of modifying sqlite_master for add_foreign_keys"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/sqlite-utils/pull/584#issuecomment-1683145819", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/584", "id": 1683145819, "node_id": "IC_kwDOCGYnMM5kUsRb", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T00:17:26Z", "updated_at": "2023-08-18T00:17:26Z", "author_association": "OWNER", "body": "Updated documentation: https://sqlite-utils--584.org.readthedocs.build/en/584/python-api.html#adding-foreign-key-constraints", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1855838223, "label": ".transform() instead of modifying sqlite_master for add_foreign_keys"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/sqlite-utils/pull/584#issuecomment-1683164661", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/584", "id": 1683164661, "node_id": "IC_kwDOCGYnMM5kUw31", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T00:45:53Z", "updated_at": "2023-08-18T00:45:53Z", "author_association": "OWNER", "body": "More updated documentation:\r\n- https://sqlite-utils--584.org.readthedocs.build/en/584/reference.html#sqlite_utils.db.Table.transform\r\n- https://sqlite-utils--584.org.readthedocs.build/en/584/python-api.html#python-api-transform-add-foreign-key-constraints", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1855838223, "label": ".transform() instead of modifying sqlite_master for add_foreign_keys"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/sqlite-utils/issues/586#issuecomment-1683396150", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/586", "id": 1683396150, "node_id": "IC_kwDOCGYnMM5kVpY2", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T06:02:18Z", "updated_at": "2023-08-18T06:06:31Z", "author_association": "OWNER", "body": "More notes in here:\r\n- https://github.com/simonw/datasette-edit-schema/issues/35#issuecomment-1683392873\r\n\r\nNot all Python/SQLite installations exhibit this problem by default!\r\n\r\nIt turns out this is controlled by the `legacy_alter_table` pragma: https://sqlite.org/pragma.html#pragma_legacy_alter_table\r\n\r\nIf that PRAGMA is turned on (default in newer SQLites) then `alter table` will error if you try to rename a table that is referenced in a view.\r\n\r\nHere's a one-liner to test if it is on or not:\r\n\r\n```bash\r\npython -c 'import sqlite3; print(sqlite3.connect(\":memory:\").execute(\"PRAGMA legacy_alter_table\").fetchall())'\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1856075668, "label": ".transform() fails to drop column if table is part of a view"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/sqlite-utils/issues/586#issuecomment-1683398866", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/586", "id": 1683398866, "node_id": "IC_kwDOCGYnMM5kVqDS", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T06:05:50Z", "updated_at": "2023-08-18T06:06:42Z", "author_association": "OWNER", "body": "Options:\r\n- Provide a `recreate_views: bool` parameter to `table.transform()` controlling if views that might reference this table are stashed and dropped and recreated within a transaction as part of the operation. But should that be `True` or `False` by default?\r\n- Read that `PRAGMA` and automatically do that view workaround if it's turned on\r\n- Toggle that `PRAGMA` off for the duration of the `.transform()` operation and on again at the end. Does it only affect the current connection?\r\n- Try the `transform()` in a transaction, detect the `\"error in view\"`, `\"no such table\"`error, if spotted then do the VIEW workaround and try again\r\n\r\nI'm on the fence as to which of these I like the most. I'm tempted to go with the one which just drops VIEWS and recreates them all the time, because it feels simpler.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1856075668, "label": ".transform() fails to drop column if table is part of a view"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/sqlite-utils/issues/586#issuecomment-1683404978", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/586", "id": 1683404978, "node_id": "IC_kwDOCGYnMM5kVriy", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T06:13:46Z", "updated_at": "2023-08-18T06:13:46Z", "author_association": "OWNER", "body": "I shipped the view recreating fix in `datasette-edit-schema`, so at least I can start exercising that fix and see if it has any weird issues.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1856075668, "label": ".transform() fails to drop column if table is part of a view"}, "performed_via_github_app": null}
{"html_url": "https://github.com/simonw/sqlite-utils/issues/577#issuecomment-1684235760", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/577", "id": 1684235760, "node_id": "IC_kwDOCGYnMM5kY2Xw", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-18T17:43:11Z", "updated_at": "2023-08-18T17:43:11Z", "author_association": "OWNER", "body": "Here's a new plugin that brings back the `sqlite_master` modifying version, for those that can use it:\r\n\r\nhttps://github.com/simonw/sqlite-utils-fast-fks", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1817289521, "label": "Get `add_foreign_keys()` to work without modifying `sqlite_master`"}, "performed_via_github_app": null}