← Back
FeaturedTechIndustry#Datasette#LLM#SQLite#sqlite-utils#Database Migration

sqlite-utils 4.0 Released with Database Schema Migrations

Simon Willison 博客(Atom)T2
03:3207/08, 03:32 AMCommunityRead the original ↗Export MD ↓

Score

77
Information gain7 ×0.3

对于已有sqlite-utils使用经验的开发者,新功能细节有增量信息;但广为人知的背景信息较多。

Content quality9 ×0.2

文章结构清晰,包含代码示例、设计讨论和发布说明,质量高。

Practical value8 ×0.2

对使用sqlite-utils的开发者有直接实用价值,尤其是迁移和嵌套事务功能。

Timeliness10 ×0.15

发布于2026年7月7日,非常及时。

Source reliability10 ×0.15

作者是工具的原作者,信息可靠且权威。

Base 85 × T2 Source weight ×0.9 = 77

Curation line 70Selected

Why selected

Rich in content and practical, detailing migration system design and usage with comparisons to Django migrations.

AI commentary

「If you're a heavy SQLite user, especially building apps with Datasette or LLM, this update is a must-read. The migration system makes schema changes safer and more traceable, while nested transactions and compound foreign keys fill long-standing gaps.」

Quick overview

sqlite-utils 4.0, the 124th release and first major version bump since 3.0, introduces database schema migrations, nested transactions via db.atomic(), and compound foreign key support. Migrations are defined in Python files using the sqlite-utils library, with a powerful table.transform() method. The release also includes breaking changes and automatic column type detection for CSV/TSV imports.

  • Introduces database schema migration system to define and apply sequential changes.
  • Nested transaction support via db.atomic() context manager.
  • Compound foreign key support including creation, transformation, and introspection.
  • Breaking changes: upserts use new SQL syntax, db.query() now only executes SELECT statements.

Full text

Showing the source text; the full translation is being generated.

This morning I released sqlite-utils 4.0, the 124th release of that project and the first major version bump since 3.0 in November 2020. In addition to some small but significant breaking changes (described in this upgrade guide), this version introduces three major features: database migrations, nested transactions (via a new db.atomic() method), and support for compound foreign keys.

Database schema migrations using sqlite-utils

Schema migrations define a sequence of changes to be made to a SQLite database, plus a mechanism for tracking which migrations have been applied and applying any that are found to be pending.

Migrations are defined in Python files using the sqlite-utils Python library, which includes a powerful table.transform() method providing enhanced alter table capabilities that are not supported by SQLite's ALTER TABLE statement.

(table.transform() implements the pattern recommended by the SQLite documentation - create a new temporary table with the new schema, copy across the data, then drop the old table and rename the temporary one in its place.)

Here's an example migration file which creates a table called creatures, adds an additional column to it in a second step, then changes the types of two of the columns in a third:

from sqlite_utils import Migrations

migrations = Migrations("creatures")

@migrations()
def create_table(db):
    db["creatures"].create(
        {"id": int, "name": str, "species": str},
        pk="id",
    )

@migrations()
def add_weight(db):
    db["creatures"].add_column("weight", float)

@migrations()
def change_column_types(db):
    db["creatures"].transform(types={"species": int, "weight": str})

Save that as migrations.py and run it against a fresh database like this:

uvx sqlite-utils migrate data.db migrations.py

Then if you check the schema of that database:

uvx sqlite-utils schema data.db

You'll see this SQL:

CREATE TABLE "_sqlite_migrations" (
   "id" INTEGER PRIMARY KEY,
   "migration_set" TEXT,
   "name" TEXT,
   "applied_at" TEXT
);
CREATE UNIQUE INDEX "idx__sqlite_migrations_migration_set_name"
    ON "_sqlite_migrations" ("migration_set", "name");
CREATE TABLE "creatures" (
   "id" INTEGER PRIMARY KEY,
   "name" TEXT,
   "species" INTEGER,
   "weight" TEXT
);

The _sqlite_migrations table is used to keep track of which migration functions have been run. The creatures table above is the schema after all three migrations have been applied.

To see a list of migrations, both pending and applied, run this:

uvx sqlite-utils migrate data.db migrations.py --list

Output:

Migrations for: creatures

  Applied:
    create_table - 2026-07-07 17:58:41.360051+00:00
    add_weight - 2026-07-07 17:58:41.360608+00:00
    change_column_types - 2026-07-07 18:01:15.802000+00:00

  Pending:
    (none)

If you don't specify a migrations file, the sqlite-utils migrate data.db command will scan the current directory and its subdirectories for files called migrations.py and apply any Migrations() instances it finds in them.

You can also execute migrations from Python code using the migrations.apply(db) method, which is useful for building tools that manage their own database schemas over multiple versions. My own LLM tool has been using a version of this pattern for several years now, as shown in llm/embeddings_migrations.py.

Prior art

My favorite implementation of this pattern remains Django's Migrations, developed by Andrew Godwin based on his earlier project South. Fun fact: Andrew, Russ Keith-Magee, and I presented our competing approaches to schema migrations for Django on the Schema Evolution panel at the very first DjangoCon back in 2008! My attempt was called dmigrations, developed with a team at Global Radio in London.

Django's migrations can be automatically generated from model definitions and include the ability to roll back to a previous version. The sqlite-utils approach is deliberately simpler: unlike Django, sqlite-utils encourages programmatic table creation rather than a model definition ORM, so there isn't anything we can use to automatically generate migrations.

I decided to skip rollback, since in my experience it's a feature that is rarely used. With a SQLite project, an easy way to achieve rollback is to create a copy of your database file before you apply the migrations!

Migrating from sqlite-migrate

The design of sqlite-utils migrations is three years old now - I had originally released it as a separate package called sqlite-migrate, which never quite graduated beyond a beta release.

I've used that package in enough places now that I'm confident in the design, so I've decided to promote it to a feature of sqlite-utils to make it available by default to all of the other tools in the growing sqlite-utils/Datasette/LLM ecosystem.

I made one last release of sqlite-migrate, which switches it to depend on sqlite-utils>=4 and replaces the __init__.py file with the following:

from sqlite_utils import Migrations

__all__ = ["Migrations"]

Any existing project that depends on sqlite-migrate should continue to work without alterations.

Everything else in sqlite-utils 4.0

Here are the release notes for this version, with some inline annotations:

The 4.0 release includes some minor backwards-incompatible fixes (hence the major version number bump) and introduces three major new features:

I think of migrations as the signature new feature, hence this blog post.

sqlite-utils has long had a confused relationship with database transactions, partly because when I started designing the library back in 2018 I didn't yet have a great feel for how those worked in SQLite itself.

Adding migrations to the core library made me determined to finally crack this nut, since transactions make migration systems a whole lot safer and easier to reason about.

I ended up building this around a db.atomic() context manager which looks like this:

with db.atomic():
    db.table("dogs").insert({"id": 1, "name": "Cleo"}, pk="id")
    db.table("dogs").insert({"id": 2, "name": "Pancakes"})

SQLite supports Savepoints, and as a result db.atomic() can be nested to carry out transactions inside of transactions. It's pretty neat!

This came about when I asked a coding agent to review all open issues and PRs for things that should be included in a 4.0 release since they would represent breaking changes if I added them later, and it correctly identified that compound foreign keys were exactly that kind of feature.

I started with a breaking change to the table.foreign_keys introspection method, and then decided to see if Claude Fable 5 could handle the more fiddly job of integrating compound foreign key creation into the library. The API design it helped create felt exactly right to me - consistent with how the rest of the library worked already.

Other notable changes include:

  • Upserts now use SQLite’s INSERT ... ON CONFLICT ... DO UPDATE SET syntax, detect existing table primary keys automatically and reject records that are missing required primary key values. (#652)

This was the change that first pushed me to consider a breaking-change 4.0 version bump. I built this to help support sqlite-chronicle, which uses triggers to keep track of rows in a table that have been inserted, updated or deleted.

  • db.query() now executes immediately and rejects statements that do not return rows; use db.execute() for writes and DDL.

Probably the most disruptive breaking change - I've had to update a few places in my own code to switch from db.query() to db.execute() as a result.

  • CSV and TSV imports now detect column types by default, while inserts into existing tables preserve those tables’ column types. (#679)

The sqlite-utils insert data.db creatures creatures.csv --detect-types flag was a later addition to allow column types (text, integer, real) to be automatically detected based on the data in a CSV. It should be the default, and releasing a 4.0 means I can make it so.

  • table.extract() and extracts= no longer create lookup table records for all-null values. (#186)

The oldest issue addressed by this release - the underlying bug was opened (by me) in October 2020.

See Upgrading from 3.x to 4.0 for details on backwards-incompatible changes.

The detailed release notes for the features and fixes shipped during the 4.0 pre-release cycle are available in 4.0a0, 4.0a1, 4.0rc1, 4.0rc2, 4.0rc3 and 4.0rc4.

The upgrade guide was entirely written by Claude Fable 5, Claude Opus 4.8 and GPT-5.5. The same is true of the release notes.

This is the kind of documentation I've slowly become comfortable outsourcing to the robots. It doesn't need to convince people of anything, or express any opinions - its job is to be as accurate and detailed as possible. I've reviewed the release notes closely and can confirm they are accurate and comprehensive.

Claude Fable 5 helped a lot

I released the first alpha of sqlite-utils 4.0 over a year ago. I've been dragging my heels on the stable release because of the amount of work it would take to track down and clean up the many other minor design flaws that a major version number allowed me to take on.

Assistance from Claude Fable 5 (and to a lesser extent Opus 4.8 and GPT-5.5) gave me just the boost I needed to overcome inertia and make the most of the time I could afford to spend on this library.

Fable has really good taste in API design, and is relentlessly proactive if you give it a more open goal. My most successful prompt was a review task that I issued against what I thought was the last release candidate:

review the changes on main since the last tagged 3.x release - I am about to ship them as sqlite-utils 4.0, a stable version that promises no backwards-incompatible fixes for a very long time.

review the changelog and upgrade guide, and write yourself scratch scripts to try out all of the new features in v4 - save those scripts but don't commit them

I tried this with GPT-5.5 xhigh in Codex Desktop and Fable 5 in Claude Code.

GPT-5.5 wrote 5 Python scripts and didn't turn up anything particularly interesting - its final report is here.

Fable 5 wrote 12 scripts, identified 4 release blockers and 10 additional issues in its report, and built a neat combined repro script, which, when run, output the following:

=== 1. Failed db.execute() write leaves an implicit transaction open ===
  in_transaction after failed write: True
  BUG: table 'other' silently lost when connection closed

=== 2. Leading ';' bypasses the query() first-token scanner ===
  BUG: raised OperationalError: no such savepoint: sqlite_utils_query
  BUG: row persisted despite rollback (count=1)

=== 3. Rejected write PRAGMA via query() still takes effect ===
  BUG: user_version=5 after 'rejected' statement (docs say no effect)

=== 4. Implicit compound FK resolves pk columns in table order, not PK order ===
  BUG: other_columns reported as ('b', 'a'), should be ('a', 'b')
  BUG: transform of valid data raised IntegrityError: FOREIGN KEY constraint failed

=== 5. ForeignKey (now a dataclass) is no longer hashable ===
  BUG: cannot use 'sqlite_utils.db.ForeignKey' as a set element (unhashable type: 'ForeignKey')

=== 6. Mixed ForeignKey objects and tuples in foreign_keys= rejected ===
  BUG: foreign_keys= should be a list of tuples

=== 7. insert --csv into an EXISTING table transforms its column types ===
  BUG: existing zip '01234' is now 1234 (column type: int)

=== 8. insert(pk=, alter=True) regression: InvalidColumns before alter runs ===
  BUG: InvalidColumns: Invalid primary key column ['id'] for table t with columns ['a']

=== 9. migrate --stop-before an already-applied migration applies everything ===
  BUG: m2 was applied despite --stop-before m1 (m1 already applied)

=== 10. ensure_autocommit_on() silently commits an open transaction ===
  BUG: row survived rollback (count=1) - transaction was committed

I found myself agreeing with almost all of them. Here's the PR with 16 commits where we worked through them in turn.

There's no doubt in my mind that sqlite-utils 4.0 is a significantly higher-quality release than if I had built it without the assistance of the latest frontier models.

Tags: schema-migrations, projects, sqlite, ai, sqlite-utils, annotated-release-notes, generative-ai, llms, ai-assisted-programming, anthropic, claude, agentic-engineering, claude-mythos-fable

Related items

89

Five-dimension score

Information gain9 ×0.3

基于真实用户行为数据的研究成果,提供了关于AI如何改变工作内容的新定量证据。

Content quality8 ×0.2

结构清晰,关键发现明确,但可进一步深入细分行业差异。

Practical value8 ×0.2

为企业管理层、HR和组织设计者提供了直接可参考的数据洞察。

Timeliness10 ×0.15

研究刚于近日发布,紧贴当前AI应用热潮。

Source reliability10 ×0.15

来源为OpenAI官方研究,数据可靠且权威。

Base 89 × T1 Source weight ×1 = 89

Curation line 60Selected

11:30OpenAI:News(RSS)T1RSS

FeaturedOpenAI Research: AI Is Redefining Job Boundaries

OpenAI研究:AI正在重新定义工作边界

If you care about the real impact of AI on work, this research is a must-read. Instead of theory, it uses data to show how ChatGPT users expand their task scope, offering direct insights for HR, managers, and every professional looking to upgrade their skills.

AIIndustry#ChatGPT#OpenAI#Labor Market Impact
87

Five-dimension score

Information gain8 ×0.3

提供了来自 AI 原生企业内部 CFO 的一手经验,信息增量较高。

Content quality9 ×0.2

内容结构清晰,来自权威高管,质量上乘。

Practical value8 ×0.2

为财务高管提供了可落地的经验与注意事项,实用价值强。

Timeliness9 ×0.15

刚发布的官方内容,时效性高。

Source reliability10 ×0.15

信源为 OpenAI 官方博客,可靠性极高。

Base 86.5 × T1 Source weight ×1 = 87

Curation line 60Selected

01:00OpenAI:News(RSS)T1RSS

FeaturedOpenAI CFO Shares Five Lessons for Building an AI-Native Finance Function

OpenAI CFO 分享构建 AI 原生财务职能的五大经验

As an editor, I find this article uniquely valuable because it comes from a finance chief at an AI leader, not an external consultancy, making it highly practical. I recommend it to finance digitalization leads, CFOs, and anyone interested in AI-driven transformation.

FinanceIndustry#AI-native finance#AI ROI#OpenAI
87

Five-dimension score

Information gain8 ×0.3

首次系统性报告智能体AI在科学计算中的具体应用案例,信息增量较高。

Content quality9 ×0.2

来自权威机构OpenAI,内容结构清晰,案例详实,质量上乘。

Practical value8 ×0.2

为科研人员和决策者提供了可操作的参考和路线图,实用价值显著。

Timeliness9 ×0.15

报告发布日期为近期,紧贴AI agent热门趋势,时效性强。

Source reliability10 ×0.15

OpenAI作为AI领域头部机构,其官方报告可信度极高。

Base 86.5 × T1 Source weight ×1 = 87

Curation line 60Selected

01:00OpenAI:News(RSS)T1RSS

FeaturedScientific Computing in the Age of Agentic AI: OpenAI Releases New Field Report

智能体AI时代下的科学计算:OpenAI发布新领域报告

This report is one of the most practical analyses in the current AI agent wave, especially suitable for practitioners in computational biology and drug discovery. It answers the key question 'What can AI agents do besides writing code?' and directly demonstrates productivity gains in complex scientific computing.

AIIndustry#AI Coding Agent#OpenAI#Genomics
87

Five-dimension score

Information gain8 ×0.3

语音人类自然度评估标准新工具,信息增量高,此前缺乏同类开源基准。

Content quality9 ×0.2

内容详实,技术细节清晰,附带使用说明与开源代码,质量高。

Practical value9 ×0.2

直接可用于模型开发与对比,实用价值极高,尤其对语音相关团队。

Timeliness8 ×0.15

语音AI快速发展期,评测需求强烈,发布时机恰逢其时。

Source reliability10 ×0.15

Hugging Face官方博客,权威信源,工具已开源可验证。

Base 87 × T1 Source weight ×1 = 87

Curation line 60Selected

08:00Hugging Face:Blog(RSS)T1RSS

FeaturedReal World VoiceEQ: Measuring the Human Quality of Voice AI

Real World VoiceEQ:量化语音AI的人类自然度

What's noteworthy is that this benchmark truly focuses on the soft metric of 'human-likeness', not traditional speech recognition rate or MOS scores. For teams working on speech synthesis, voice cloning, or voice interaction, this is a highly practical reference tool that quickly identifies model deficiencies in emotion, prosody, etc. However, language support is currently limited; looking forward to future expansion.

TechIndustry#Hugging Face#Real World VoiceEQ#Naturalness Evaluation
85

Five-dimension score

Information gain8 ×0.3

提出新颖的AI ROI量化框架,对当前缺乏标准化评估方法的企业具有较高的信息增量。

Content quality7 ×0.2

框架逻辑清晰,但缺乏具体案例或数据支撑,偏概念性介绍。

Practical value9 ×0.2

直接面向企业实际痛点,可操作性强,对投资决策有明确指导意义。

Timeliness10 ×0.15

在当前AI投资泡沫与理性化需求背景下,该内容极其及时。

Source reliability9 ×0.15

来自OpenAI官方渠道,由CFO署名,权威性高。

Base 84.5 × T1 Source weight ×1 = 85

Curation line 60Selected

18:00OpenAI:News(RSS)T1RSS

FeaturedOpenAI Introduces AI Age Scorecard: A New Way to Measure ROI

OpenAI 推出AI时代记分卡:衡量投资回报的新方法

This blog post hits a sore point in the current AI industry: how to quantify input-output. For enterprise decision-makers deploying or planning large-scale AI, it helps avoid herd mentality. My personal advice: try defining 'useful work' based on your own business scenario, and avoid designing metrics that are merely formality.

TechIndustry#AI Scorecard#AI Deployment Strategy#OpenAI
85

Five-dimension score

Information gain8 ×0.3

提出了新颖的'每美元有用功'指标,对AI投资评估有较高信息增量。

Content quality7 ×0.2

逻辑清晰、结构完整,但缺少实际案例分析,略偏抽象。

Practical value9 ×0.2

为企业提供了可落地的衡量框架与治理建议,实用性强。

Timeliness9 ×0.15

发表于代理时代兴起之际,贴合当前企业AI投资焦虑点。

Source reliability10 ×0.15

来源为OpenAI官方博客,权威性高。

Base 84.5 × T1 Source weight ×1 = 85

Curation line 60Selected

18:00OpenAI:News(RSS)T1RSS

FeaturedHow to Manage AI Investments in the Agentic Era: Focus on Useful Work per Dollar and High-Value Workflows

代理时代如何管理AI投资:聚焦每美元有用功与高价值工作流

This article is especially important for enterprises scaling AI agents. It reminds us that focusing solely on falling API prices is a trap; what really matters is the return per unit of work. I recommend CTOs and CFOs read it together and establish quantitative evaluation mechanisms internally.

AIIndustry#AI agents#OpenAI#ROI
85

Five-dimension score

Information gain8 ×0.3

提出组织延迟这一概念,对AI落地实践有增量启发。

Content quality8 ×0.2

观点明确,逻辑清晰,引用权威信源。

Practical value9 ×0.2

对企业和组织管理者具有直接指导意义。

Timeliness8 ×0.15

当前AI快速迭代期,组织滞后问题紧迫。

Source reliability10 ×0.15

来源为OpenAI官方新闻,权威性高。

Base 85 × T1 Source weight ×1 = 85

Curation line 60Selected

20:00OpenAI:News(RSS)T1RSS

FeaturedDesigning Organisations That Can Keep Up With AI: Organisational Latency as the Biggest Barrier

设计能跟上AI的组织:组织延迟成为最大障碍

What makes this article truly worth following is that it raises a neglected bottleneck: no matter how strong the technology is, if the organisation can't keep up, it's useless. For CTOs, CIOs, and transformation leaders, this is a more important framework than tech selection.

AIIndustry#AI#OpenAI#Organisational Change
84

Five-dimension score

Information gain9 ×0.3

本文披露 Ruff 默认规则的大幅扩展及具体迁移方法,对 Python 开发者有较高信息增量

Content quality9 ×0.2

结构清晰,包含具体统计数据、命令示例和 AI 集成案例,质量上乘

Practical value10 ×0.2

提供了可立即执行的一行命令和修复策略,对真实开发场景有直接帮助

Timeliness10 ×0.15

文章发布于版本发布后两天,非常及时

Source reliability9 ×0.15

来自知名技术博主 Simon Willison,长期维护多个 Python 项目,信源可靠

Base 93.5 × T2 Source weight ×0.9 = 84

Curation line 70Selected

06:44Simon Willison 博客(Atom)T2Community

FeaturedRuff v0.16.0 Released: Default Rules Jump from 59 to 413, CI Pipelines Need Attention

Ruff v0.16.0 发布:默认规则从59条激增至413条,CI工作流需关注

For Python developers, this is an unmissable update signal. The significant increase in Ruff's default rules means we can no longer ignore potential issues in our code, and AI fix tools can lower the upgrade cost. Notably, Astral (OpenAI) is deeply integrating AI coding capabilities with static analysis tools, which may become the norm in future engineering practices.

TechProducts#Astral#CI/CD#OpenAI

Verify against the original source before citing