sqlite-utils 4.0rc2、主にClaude Fable(約149.25ドル分)が執筆
本文の状態
日本語全文を表示中
詳細モードで約16分の本文を読めます。
同じ出来事の情報源
この情報源を基点に整理
Simon Willison Blog
Simon Willison氏が、Claude Fableの契約期間終了前に利用し、SQLiteユーティリティライブラリ「sqlite-utils」のバージョン4.0安定版に向けた作業を完了させるため、同AIに主に執筆を依頼したことを報告している。
Source Article
元記事を日本語で読む
本文に関係しない購読案内、埋め込み通知、サイト内プロモーションは除いています。
数週間前に、sqlite-utils 4.0rc1 のリリースについて記事を書きました。Max サブスクリプションで Claude Fable を利用できるのはあと数日だけなので、この機会に 4.0 の安定版リリースに向けてサポートしてもらおうと決めました。私は SemVer(Semantic Versioning)を遵守することを心がけており、互換性を破壊するメジャーバージョンは極力減らしたいと考えているからです。
まずは iPhone でウェブ版の Claude Code に、以下のようなプロンプトを入力しました。
安定版 4.0 のリリース前に最終レビューを行うこと - 後で修正すると破壊的変更になるような最後の瞬間の問題を徹底的に見つけることが極めて重要
私が作成した初期レポートはこちらです。このレポートには、私自身はまだ遭遇していなかったいくつかの重大な問題が記載されていました。特に Fable 氏が「リリース阻止要因」と分類したものは 5 つあります。その中でも最も深刻なものを以下に挙げます。
1.
delete_where()はコミットせず、接続を汚染する(データ消失のリスク)
Table.delete_where() (sqlite_utils/db.py:2948) はその実行中、
自前の self.db.execute() を直接使用して DELETE する際、引数なしで実行することはできません。
atomic() ラッパー — db.py:2944 の Table.delete() と比較すると、こちらは正しくラップされていることがわかります。
接続状態は in_transaction=True のままにされるため、その後のすべての atomic() 呼び出しはセーブポイント分岐(db.py:430-440)をたどり、いずれもコミットされません。これをエンドツーエンドで再現したものが以下です:
db = sqlite_utils.Database("dw.db")
db["t"].insert_all([{"id": i} for i in range(3)], pk="id")
db["t"].delete_where("id = ?", [0]) # conn.in_transaction is now True
db["t"].insert({"id": 50})
db["u"].insert({"a": 1})
db.close()
# Reopen: rows are [0, 1, 2] — the delete, row 50, AND table u are all gone.これは非常に深刻なバグでした!リリースしてしまわなくて本当に良かった。もしリリースしていたとしても、4.0.1 のポイントリリースで修正可能なバグだったはずです。しかし、5.0 への移行を余儀なくさせる設計上の欠陥ではなかったのです。
37 回のプロンプト、34 コミット、合計 +1,321 行の追加と -190 行の削除という変更が 30 の別々のファイルにわたって行われました。フィードバックを順次処理する過程で、いくつかの設計上の改善も併せて実現できました。
コーディングエージェントを使う際の特徴として、このような難易度の高いタスクの方が、実は同時に他の作業をする機会が増えるという逆説的な側面があります。なぜなら、エージェントが新しいタスクに取り組むのに 10〜15 分かかる場合があるからです。私はその間、ハーフムーンベイの独立記念日のパレードを楽しみながら、スマホから時々確認を入れ、Fable に次のステップを指示していました。
詳細は PR と 共有されたトランスクリプト でご覧いただけます。最終レビューはラップトップに移行し、GitHub の PR インターフェースを通じて実施しました。
最も大きな変更点はトランザクション処理に関するもので、これは 以前の RC で紹介された目玉の新機能です。今回の新しい RC では、包括的なドキュメント が追加され、新しいトランザクションモデルについて解説されています。その導入部分の全文を以下に引用します:
このライブラリでデータベースに書き込むすべてのメソッドは、
insert()、upsert()、update()、delete()、delete_where()、transform()、create_table()、create_index()です。
enable_fts() とその他の処理は、独自のトランザクション内で実行され、返却する前にコミットされます。
メソッド呼び出しが完了すると、変更は即座にディスクに保存されます。
db = Database("data.db")
db.table("news").insert({"headline": "Dog wins award"})
# The new row is already saved - no commit() requireddb.execute() で実行される生 SQL についても同様で、書き込み文の実行が完了した時点でコミットされます。
コミット処理を明示的に呼び出す必要はありませんし、変更を保存するためにデータベースを閉じる必要もありません。トランザクションを意識すべき状況は、以下の 2 つだけです。
1 つ目は、複数の書き込み操作をまとめて実行したい場合です。これにより、すべての操作が成功するか、あるいはすべて失敗するかのどちらかになります。この場合は db.atomic() を使用してください。
2 つ目は、db.begin() を使って自分でトランザクションを管理している場合です。このケースでは、明示的にコミットするまで何事も確定しません。ライブラリ側が勝手に開いたトランザクションをコミットすることはありません。 (原文の技術表記: commit())
Fable のドキュメントを確認したところ、変更点の概要を把握するにはまずドキュメントの改訂履歴を見ることが非常に有効だと感じました。例えば、こちら の記述に注目しました。
db.atomic()とメソッドごとの自動トランザクションは、Python のデフォルトのトランザクション処理モードにおける接続を対象に設計されています。
Python 3.12 以降の sqlite3.connect(..., autocommit=True) または autocommit=False オプションで作成された接続は、commit() や
rollback() は、それらの接続において異なる挙動を示します。
私は、Python 3.12 で追加された最新の autocommit 設定 に対して sqlite-utils がどのように反応するかについては考慮していませんでした。その結果、「接続先によって挙動が変わる」ということが、ほぼすべてのテストスイートの失敗に直結することが判明しました。そこで、このライブラリの動作を壊さないよう、モデルと協力して対応策を講じました。
この違いがどのように解決されたか の詳細は上記リンクをご確認ください。
GPT-5.5 による最終レビュー
以前は、あるモデルが別のモデルの作業を検証するというアイデアに懐疑的でした。まるで奇妙な迷信のように感じられたからです。しかし実際には*これが機能するのです*。私は今や習慣的に、Anthropic の最上位モデルで OpenAI の成果を検証し、その逆も行うようになりました。これまでに何度も興味深い結果が得られ、実用的な価値があることが分かったからです。
Codex Desktop と GPT-5.5 xhigh に対して、以下のプロンプトを入力しました。
直近の RC 以降の変更点をレビューしてください。また、変更履歴(changelog)が最新であることを確認してください。
この指示だけで、調査に値する 2 つの問題が見つかりました。 (原文の技術表記: Review changes since the last RC. Also confirm that the changelog is up-to-date.)
発見点 [P1]
db.query()は、db.execute()を呼び出した後にのみ非行文を拒否するようになりました。また、sqlite_utils/db.py:705 では、これらの書き込みが最初に自動的にコミットされるようになりました。
db.query("update ...") を実行すると ValueError が発生しますが、その時点で更新処理は既にコミット済みです。これは「行を返す SQL のみで使用できる」とドキュメントに記載されているメソッドにとって、予期せぬ副作用と言えます。[P1] sqlite_utils/db.py:672
db.query() を通じた INSERT ... RETURNING は、返されたジェネレーターが完全に使い切られるまでコミットされません。
db.query("insert ... returning ...") を反復処理なしで実行するか、一般的な next(db.query(...)) の使い方をすると、トランザクションが開いたままになり、クローズ時に書き込みがロールバックされる可能性があります。
これは、反復処理なしで即座に有効になると記述されている docs/changelog.rst:15 や docs/python-api.rst:232 と矛盾しています。
そのコードを新しい Fable セッションに貼り付け、問題を確認するための実験を実行しました。
両方の発見は確認されました。db.query() はまず self.execute() を呼び出して自動コミットで書き込みを行い、その後に cursor.description をチェックしています。そのため、
db.query("update ...") を実行した際、更新処理がコミットされた後に ValueError が発生していました。そして、この
INSERT ... RETURNING のコミットは、返されたジェネレーターの末尾に存在していたため、イテレーターを完全に消費しない限り実行されませんでした。つまり、next(db.query(...)) を使用するか、イテレーションを行わない呼び出しをした場合でもトランザクションが開いたままになる可能性があります。
変更ログやドキュメントが約束している内容と矛盾しています。
修正を施した PR は こちら です。また、Claude Code の完全なトランスクリプトは こちら で確認できます。このコードをレビューしたことで、SQLite のトランザクションセマンティクスにおけるエッジケースに対する理解が深まりました。
推定(補助金なし)の費用は149.25ドル
私はClaude Maxの月額200ドルプランへアップグレードしました。以前は月額100ドルでした。これは、7月7日の「Fablepocalypse」まで残っている期間中、Claude Maxのサブスクリプションユーザーであってもモデルの利用料を全額負担しなければならないようになる前に、Fableの使用枠を増やすためです。
もし私がこれらの利用料を直接支払っていた場合、いくらかかるのか気になりました。当初は、ウェブ版のClaude Codeを使って遠隔で作業を行ったため、その数値が把握できないのではないかと考えました。しかしその後、既存のセッション内でAgentsViewを実行すれば、利用料の見積もりを取得できることに気づきました!
uvx agentsview --help を実行し、そのツールを使って今回のセッションのコストを計算してください
Claude は session list --include-children コマンドの使い方を理解し、以下のような結果を出しました。 (原文の技術表記: Run "uvx agentsview --help" and then use that tool to calculate the cost of this session)
| 通訳 | モデル | コスト |
|---|---|---|
| メインセッション | claude-fable-5 | $141.02 |
| API 表面スウィープエージェント | claude-fable-5 | $2.40 |
| トランザクション/アトミックレビューエージェント | claude-fable-5 | $2.39 |
| Post-rc1 コミットレビューエージェント | claude-fable-5 | $1.72 |
| マイグレーションレビューエージェント | claude-fable-5 | $1.40 |
| プロンプトカウントエージェント | claude-opus-4-8 | $0.32 |
| 合計 | $149.25 |
このサブスクリプションに加入して本当に良かったと思います。実際には、自分のアドバイスに従ってより安価なモデルを活用すべきだったのかもしれません。
現在、claude.ai/settings/usage で表示されている利用状況は以下の通りです。

現在、Fable を活用した他の主要プロジェクトも複数進行中で、価格改定に合わせて Fable 関連の進捗バーを 100% に到達させることを目指しています。
sqlite-utils 4.0rc2 の完全なリリースノート
RC 版の完全なリリースノートはこちらです。各変更が反映されるたびに、Fable に「Unreleased」セクションへ追加させ、その都度レビューを行いました。これにより、changelog のコミット履歴が、リリースに含まれる各変更の簡潔な要約として機能するという嬉しい副産物が生まれています。
過去には手動でリリースノートを作成する方針でしたが、正直に言って Fable が作成したものは自分自身で作るよりも優れています。リリースノートは、退屈で予測可能かつ正確であることが求められるため、エージェントへ任せるのに最適な文章です。
破壊的変更:
db.execute()で実行した文は、すでにトランザクションが開かれている場合を除き、自動的にコミットされるようになりました。それまでは、明示的にコミットするまでトランザクションが継続し、同じ接続上での読み取りには反映されたように見えても、接続が閉じると静かにロールバックされていました。
未完了の db.execute() による書き込みをロールバックすることに依存していたコードでは、新しい db.begin() メソッドを使用して明示的なトランザクションを開く必要があります。トランザクションモデルの詳細は Transactions and saving your changes で確認できます。
db.query() は、返されたジェネレーターを最初にイテレートするまで待機するのではなく、呼び出された直ちに SQL を実行するように変更されました。行の取得は依然としてイテレーション時に遅延ロードされますが、SQL エラーは呼び出し元で即座に発生します。
INSERT ... RETURNING 文は、結果を反復処理する必要なく即座に実行されコミットされます。また、行を返さないステートメントを渡した場合、以前は静かに何もしない(no-op)状態でしたが、現在は db.execute() の使用を推奨する ValueError を発生させるようになりました。
このように拒否されたステートメントは、エラーが発生する前にロールバックされるため、データベースには何の影響も与えません。
Python API のバリデーションエラーは、以前は AssertionError を発生させていましたが、現在は ValueError を発生させるようになりました。これにより、無効な引数(例えば列を指定せずに create_table() を呼び出した場合など)が正しく検出されます。
存在しないテーブルに対して transform() を実行するか、両方を同時に指定した場合にエラーが発生します。
ignore=True と replace=True は、単純な assert 文で拒否されました。Python を -O オプション付きで実行すると、これらのアサーションは静かにスキップされます。
flag. これらのケースで AssertionError を捕捉するコードは、代わりに ValueError を捕捉するように修正する必要があります。
table.upsert()およびtable.upsert_all()は、レコードに主キーが指定されていない場合、PrimaryKeyRequired例外を発生させるようになりました。
主キー列の値が欠落している、または 1 つの列に None の値が含まれている場合。
以前、既存の行と一致しないレコードは、静かに新規行として挿入されるか、挿入後に混乱を招く KeyError が発生していました。
db.enable_wal()およびdb.disable_wal()は、トランザクションがオープンしている状態で呼び出された場合、sqlite_utils.db.TransactionErrorを発生させるようになりました。
以前は、ジャーナルモードを変更する副作用としてオープンなトランザクションが黙ってコミットされてしまい、db.atomic() のロールバック保証が破られていました。
ユーザーが管理するトランザクション。
Viewクラスからenable_fts()メソッドが削除されました。これはフルテキスト検索がビューではサポートされていないため、常にNotImplementedErrorを発生させるために存在しただけのメソッドでした。
呼び出すと、代わりに AttributeError が発生し、そのメソッドが利用できなくなります。
API リファレンスからは削除されました。sqlite-utils enable-fts コマンドは、ビューを指定された場合に明確なエラーメッセージを表示します。
CSV や TSV データに対しては、型検出がバージョン 4.0a1 からデフォルトで有効になっているため、insert および upsert コマンドから意味をなさない -d/--detect-types フラグが削除されました。
これを利用する際は、単に省略すればよいです。検出を無効化するには --no-detect-types を使用できます。
Database()は、Python 3.12 以降で生成された接続を引数として渡された場合、sqlite_utils.db.TransactionErrorを発生させるようになりました。
sqlite3.connect(..., autocommit=True) または autocommit=False オプション。
commit() と rollback() は、接続ごとに挙動が異なります。この違いにより、以前はライブラリが行ったすべての書き込み操作が、接続が閉じられた際に静かに破棄されてしまうという問題が発生していました。
その他:
table.delete_where()、table.optimize()、table.rebuild_fts()の各メソッドで変更がコミットされず、接続がトランザクション内に残ったままになるバグを修正しました。
このようにして、接続が閉じられた際に彼らの作業(およびその後の書き込み)は静かにロールバックされます。これら 3 つの機能はすべて db.atomic() を使用しており、他の書き込みメソッドと整合性が保たれています。
sqlite-utils drop-tableコマンドは、ビューの削除を拒否するようになりました。同様にdrop-viewはテーブルの削除も拒否します。以前は名前が一致すれば、それぞれ誤った種類のオブジェクトを静かに削除していましたが、現在は正しいコマンドの使用を促すエラーで終了します。
新しい マイグレーションシステム によって適用されるマイグレーションは、現在トランザクション内で実行され、その適用記録も同時に保存されます。もしマイグレーション中に例外が発生した場合、その変更はロールバックされ、マイグレーションは未完了の状態で残ります。これにより、エラーが修正された後に安全に再適用することが可能になります。
VACUUM を実行するなど、トランザクション内で動作させることができないマイグレーションについては、@migrations(transactional=False) を使用してこの挙動を無効化できます。詳細は
原文を表示
I wrote about the sqlite-utils 4.0rc1 release a couple of weeks ago. Since we only have Claude Fable on our Max subscriptions for a few more days, I decided to see if it could help me get to a 4.0 stable release that I felt truly comfortable about, since I try to keep to SemVer and like my incompatible major versions to be as rare as possible.
I started with this prompt, in Claude Code for web on my iPhone:
Final review before shipping a stable 4.0 release - very important to spot any last minute things that would be a breaking change if we fix them later
Here's that initial report it created for me. There were some *significant* problems that I hadn't myself encountered yet - 5 that Fable categorized as "release blockers". Here's the worst of the bunch:
1. delete_where() never commits and poisons the connection (data loss)
Table.delete_where() (sqlite_utils/db.py:2948) runs its DELETE via a bare self.db.execute() with no atomic() wrapper — compare Table.delete() at db.py:2944, which wraps correctly. The connection is left in_transaction=True, so every subsequent atomic() call takes the savepoint branch (db.py:430-440) and never commits either.
Reproduced end-to-end:
`
db = sqlite_utils.Database("dw.db")
db["t"].insert_all([{"id": i} for i in range(3)], pk="id")
db["t"].delete_where("id = ?", [0]) # conn.in_transaction is now True
db["t"].insert({"id": 50})
db["u"].insert({"a": 1})
db.close()
Reopen: rows are [0, 1, 2] — the delete, row 50, AND table u are all gone.
That's a really bad bug! Very glad I didn't ship that, although at least it would have been a bug I could fix in a 4.0.1 point release, not a design flaw that would force a 5.0.
Over the course of 37 prompts, 34 commits and +1,321 -190 code changes over 30 separate files, we worked through the entire set of feedback in turn, making several other design improvements along the way.
A weird thing about coding agents is that harder tasks like this one actually provide *more* opportunity to do other things at the same time, since the agent sometimes needs 10-15 minutes to churn away on a new task. I went out to enjoy the Half Moon Bay 4th of July parade, occasionally checking in and prompting the next step for Fable from my phone.
Full details [in the PR](https://github.com/simonw/sqlite-utils/pull/767) and [this shared transcript](https://claude.ai/code/session_01UnLnhsH25Nnv7LHhekUfPd). I switched to my laptop for the final review, which I conducted through GitHub's PR interface.
The most significant changes relate to transaction handling, which was the signature new feature in [the earlier RC](https://simonwillison.net/2026/Jun/21/sqlite-utils-40rc1/#new-feature-db-atomic-transactions). The new RC now includes [comprehensive documentation](https://sqlite-utils.datasette.io/en/latest/python-api.html#transactions-and-saving-your-changes) on the new transaction model, the intro to which I'll quote here in full:
> Every method in this library that writes to the database - insert(), upsert(), update(), delete(), delete_where(), transform(), create_table(), create_index(), enable_fts() and the rest - runs inside its own transaction and commits it before returning. Your changes are saved to disk as soon as the method call finishes:
> ```
db = Database("data.db")
db.table("news").insert({"headline": "Dog wins award"})
# The new row is already saved - no commit() requiredThe same applies to raw SQL executed with db.execute() - a write statement is committed as soon as it has run.
You never need to call commit(), and you do not need to close the database to persist your changes. There are exactly two situations where you need to think about transactions:
You want to group several write operations together, so they either all succeed or all fail - use db.atomic().
You are managing a transaction yourself with db.begin(), in which case nothing is committed until you commit - the library will never commit a transaction you opened.
In reviewing Fable's documentation - I find that reviewing the documentation edits first is an *excellent* way to build an initial understanding of what has changed - I spotted this detail:
db.atomic() and the automatic per-method transactions are designed for connections in Python's default transaction handling mode. Connections created with the Python 3.12+ sqlite3.connect(..., autocommit=True) or autocommit=False options are not supported, because commit() and rollback() behave differently on those connections.
I admit I hadn't thought about how sqlite-utils would react to the more recent autocommit setting, added in Python 3.12. It turns out "behave differently on those connections" equated to almost the entire test suite failing, so I worked with the model to ensure that this difference would not break how the library works.
And a final review by GPT-5.5
I used to think that the idea of having one model review the work of another was somewhat absurd - it felt weirdly superstitious. The problem is *it really does work* - I've started habitually having Anthropic's best model review OpenAI's work and vice versa, because I've had that turn up interesting results often enough to be valuable.
I prompted Codex Desktop and GPT-5.5 xhigh with the following:
Review changes since the last RC. Also confirm that the changelog is up-to-date.
Which was enough to turn up two issues worth investigating:
Findings
[P1] sqlite_utils/db.py:663 db.query() now rejects non-row statements only after calling db.execute(), and sqlite_utils/db.py:705 auto-commits those writes first. So db.query("update ...") raises ValueError but the update is already committed. That is a surprising side effect for a method documented as “can only be used with SQL that returns rows.”
[P1] sqlite_utils/db.py:672 INSERT ... RETURNING through db.query() only commits after the returned generator is fully exhausted. db.query("insert ... returning ...") without iteration, or common next(db.query(...)) usage, leaves the transaction open and the write can be rolled back on close. This contradicts docs/changelog.rst:15 and docs/python-api.rst:232, which say it takes effect without iteration.
I pasted that into a fresh Fable session, which ran some experiments to confirm the problem:
Both findings were confirmed. db.query() called self.execute() first, which auto-commits writes, and only then checked cursor.description — so db.query("update ...") committed the update before raising ValueError. And the INSERT ... RETURNING commit lived at the end of the returned generator, so it never fired unless you exhausted the iterator — next(db.query(...)) or an un-iterated call left the transaction open, contradicting what the changelog and docs promise.
Here's the PR with the fix, and the full Claude Code transcript. Reviewing this code helped me build a better mental model of the edge cases of SQLite transaction semantics!
For an estimated (unsubsidized) cost of $149.25
I upgraded to the Claude Max $200/month plan (I was previously on $100/month) to increase my Fable allowance for the remaining time until the July 7th Fablepocalypse, when even Claude Max subscribers will have to pay full API cost for the model.
I was curious as to how much this would have cost me if I had been paying those costs directly. At first I thought those numbers weren't available to me since I had run the work remotely using Claude Code for web, and then I realized I could run AgentsView inside that existing session to get that cost estimate!
Run "uvx agentsview --help" and then use that tool to calculate the cost of this session
Claude figured out how to use the session list --include-children command and came out with the following:
| Transcript | Model | Cost |
|---|---|---|
| Main session | claude-fable-5 | $141.02 |
| API-surface sweep agent | claude-fable-5 | $2.40 |
| Transactions/atomic review agent | claude-fable-5 | $2.39 |
| Post-rc1 commits review agent | claude-fable-5 | $1.72 |
| Migrations review agent | claude-fable-5 | $1.40 |
| Prompt-counting agent | claude-opus-4-8 | $0.32 |
| Total | $149.25 |
I'm very glad I'm on that subscription! I really should have followed my own advice and leaned more heavily into subagents with cheaper models.
Here's what claude.ai/settings/usage is showing me right now:

I have several other major Fable-driven projects on the go right now as well, with the goal of hitting 100% on that Fable bar just in time for the price increase.
The full release notes for sqlite-utils 4.0rc2
Here are the full release notes for the RC. I had Fable add these to an "Unreleased" section of the changelog as each change landed, reviewing them as it went. This has the neat side effect that the commit history of the changelog acts as a concise summary of each of the changes that went into the release.
In the past I've had a policy of writing release notes by hand, but honestly these are better than I would have created myself. Release notes are a great example of writing that I'm OK to outsource to agents because they need to be boring, predictable and accurate.
Breaking changes:
- Write statements executed with db.execute() are now committed automatically, unless a transaction is already open in which case they join it. Previously they opened an implicit transaction that stayed open until something committed it - writes appeared to work when read on the same connection but were silently rolled back when the connection closed. Code that relied on rolling back uncommitted db.execute() writes should use the new db.begin() method to open an explicit transaction first. The transaction model is documented in full at Transactions and saving your changes.
- db.query() now executes its SQL as soon as it is called, rather than waiting until the returned generator is first iterated. Rows are still fetched lazily during iteration. SQL errors are now raised at the call site, statements such as INSERT ... RETURNING are executed and committed immediately without needing to iterate over their results, and passing a statement that returns no rows - previously a silent no-op - now raises a ValueError recommending db.execute() instead. A statement rejected this way is rolled back before the error is raised, so it has no effect on the database.
- Python API validation errors now raise ValueError instead of AssertionError. Previously invalid arguments - such as create_table() with no columns, transform() on a table that does not exist, or passing both ignore=True and replace=True - were rejected using bare assert statements, which are silently skipped when Python runs with the -O flag. Code that caught AssertionError for these cases should catch ValueError instead.
- table.upsert() and table.upsert_all() now raise PrimaryKeyRequired if a record is missing a value for any primary key column, or has a value of None for one. Previously such records - which can never match an existing row - were quietly inserted as brand new rows, or triggered a confusing KeyError after the insert had already taken place.
- db.enable_wal() and db.disable_wal() now raise a sqlite_utils.db.TransactionError if called while a transaction is open. Previously they would silently commit the open transaction as a side effect of changing the journal mode, breaking the rollback guarantee of db.atomic() and of user-managed transactions.
- The View class no longer has an enable_fts() method. It existed only to raise NotImplementedError, since full-text search is not supported for views - calling it now raises AttributeError instead, and the method no longer appears in the API reference. The sqlite-utils enable-fts command shows a clean error when pointed at a view.
- The no-op -d/--detect-types flag has been removed from the insert and upsert commands. Type detection has been the default for CSV/TSV data since 4.0a1, so the flag did nothing - invocations using it should simply drop it. --no-detect-types remains available to disable detection.
- Database() now raises a sqlite_utils.db.TransactionError if passed a connection created with the Python 3.12+ sqlite3.connect(..., autocommit=True) or autocommit=False options. commit() and rollback() behave differently on those connections, which previously caused every write made by the library to be silently discarded when the connection closed.
Everything else:
- Fixed a bug where table.delete_where(), table.optimize() and table.rebuild_fts() did not commit their changes, leaving the connection inside an open transaction. Their work - and any subsequent writes - could then be silently rolled back when the connection was closed. All three now use db.atomic(), consistent with the other write methods.
- The sqlite-utils drop-table command now refuses to drop a view, and drop-view refuses to drop a table. Previously each would silently drop the wrong type of object if the name matched. Both now exit with an error suggesting the correct command to use.
Migrations applied by the new migrations system now run inside a transaction, together with the record of the migration having been applied. If a migration raises an exception its changes are rolled back and it stays pending, so it can be safely re-applied after the error is fixed. Migrations that cannot run inside a transaction, such as those executing VACUUM, can opt out using @migrations(transactional=False) - see
関連記事
今日のまとめ
AIデイリーブリーフで今日の重要ニュースをまとめ読み