Claude Opus 5.5 Built a SQLite parse_number() Extension: C Beat Rust on Speed Developer Simon Willison published a November 8, 2026 walkthrough showing that Claude Opus 5.5 in regular Claude chat helped him build four loadable SQLite extensions implementing the same parse_number(value) scalar function, with the hand-written C version (c-fast) reaching about 16–19 ns/row net in published benchmarks versus roughly 329–357 ns/row for a nested SQL replace() baseline. The four builds — plain C, c-fast, rusqlite and sqlite-loadable — were validated with 40 hand tests plus 8M fuzz checks with no mismatches in the published log, and the smallest binary was the plain C build at about 14 KB stripped .so. Willison's README discloses that all text and code in the report were model-generated, and the reproducible folder is published in simonw/research. On November 8, 2026 , developer Simon Willison published a walkthrough of how regular Claude chat with Opus 5.5 helped him build four loadable SQLite extensions that implement the same parse number value scalar function, benchmark them against pure SQL, and ship a reproducible folder in simonw/research https://github.com/simonw/research/tree/main/sqlite-parse-number-c-vs-rust?ref=explainx . The hook is practical: turn messy spreadsheet strings such as -$1,234.56 or 12.5% into numbers you can SUM and ORDER BY inside SQLite. This is not a product launch from Anthropic or SQLite. It is a documented agent-assisted systems programming session with numbers readers can re-run. If you build analytics pipelines, local-first apps, or agent tools that ingest CSV exports, the lesson is how far a well-scoped native extension prompt can get before you touch a full coding agent repo. TL;DR | Question | Answer | |---|---| | Which models? | Claude Opus 5.5 in Claude chat Willison's post; not Claude Code | | What function? | parse number value strips , $ £ % , trims space, handles one leading - , returns NULL on junk | | How many builds? | Four : plain C , c-fast , rusqlite , sqlite-loadable | | Fastest? | c-fast at about 16–19 ns/row net in published benches | | Smallest binary? | C at about 14 KB stripped .so | | Slowest baseline? | Nested SQL replace at ~329–357 ns/row | | Correctness? | 40 hand tests + 8M fuzz checks, no mismatches in published log | | Where is source? | GitHub research folder https://github.com/simonw/research/tree/main/sqlite-parse-number-c-vs-rust?ref=explainx | What Willison asked Opus 5.5 to do In his November 8 post https://simonwillison.net/2026/Nov/8/sqlite-parse-number-c-vs-rust/?ref=explainx , Willison says the idea started on a dog walk. He opened Claude and prompted for: - A parse number value custom function that strips $ £ % , , tolerates whitespace, allows one leading minus, and returns NULL when parsing fails. - A comparison between implementing it as a SQLite C extension versus Rust loadable modules, including performance , code complexity , and .so size . The first pass surprised him: Rust binaries were hundreds of kilobytes but faster than plain C on mixed data because strtod in glibc lost on decimals. He followed up: Try writing your own C function for parsing text into a decimal number that catches up to or beats the Rust implementation That second prompt produced c-fast/ , the eventual winner on the benchmark table. He then asked Claude to clone the simonw/research layout and deliver a zip-style research package. The published README https://github.com/simonw/research/tree/main/sqlite-parse-number-c-vs-rust?ref=explainx begins with a disclosure that all text and code in that report were model-generated , pointing readers to the parent repo's explanation of AI-authored research notes. Behavior rules the model chose Because the initial prompt left edge cases open, Opus 5.5 fixed a shared ruleset used by all four builds: - INTEGER and REAL inputs pass through unchanged; NULL and BLOB return NULL . - Strip spaces, tabs, and newlines at both ends; remove currency symbols and commas anywhere. - Allow one leading minus before the first digit; allow one decimal point with at least one digit. - Reject scientific notation, interior spaces, doubled minus signs, and ambiguous forms like 5- . Example calls from the README: select parse number ' -$1,234.56 ' ; -- -1234.56 select parse number ' £1,000,000 ' ; -- 1000000 INTEGER select parse number ' 12.5% ' ; -- 12.5 select parse number ' n/a ' ; -- NULL Those rules matter for agents. If you let a model invent parsing ad hoc in SQL, you get inconsistent dashboards. Centralizing in a scalar function gives agents a single predicate to reference in generated queries. Benchmark numbers from the research README The repo runs select sum parse number v over two million rows inside Python's sqlite3 module SQLite 3.53.1 . It subtracts a baseline scan to report net nanoseconds per row . Hardware was a 2 vCPU Linux x86-64 VM with gcc 13.3 and rustc 1.97 . | Dataset | plain C | fast C | rusqlite | sqlite-loadable | pure SQL replace | |---|---|---|---|---|---| | Mixed currency strings | 67.3 | 19.2 | 45.9 | 40.5 | 328.9 | | Integers only | 25.1 | 15.8 | 36.4 | 32.1 | 342.5 | | Decimals only | 106.3 | 17.5 | 54.5 | 46.5 | 356.9 | Willison's blog post rounds those to 16–19 ns for c-fast across suites. The takeaway for builders is not that C always wins. It is that library float parsing choices strtod versus Rust's f64::from str dominated until the model rewrote a Clinger-style fast path inside the scan loop. The README documents why plain C lost on decimals and how c-fast detects when mantissa and fraction lengths fit exact IEEE operations, falling back to strtod only on long inputs. Size and complexity trade-offs | Variant | Stripped .so | Lines of core code per README | Build time from scratch | |---|---|---|---| | plain C | 14,408 B | 75 | ~0.1 s | | c-fast | 14,408 B | 104 | ~0.1 s | | rusqlite | 320,864 B | 36 + 41 shared parser | ~11 s | | sqlite-loadable | 298,464 B | 30 + 41 shared parser | ~72 s bindgen | Rust builds drag dozens of crates even for one function. For desktop analytics that is fine. For shipping a single .so inside a mobile app or SQLite shell plugin , the 14 KB C artifact is the headline. Willison also notes Rust's ergonomics: overflow-safe integer parsing , ValueRef matches , and less manual buffer bookkeeping. The README's "Notes on writing each one" section is essentially a micro trade study you can hand to a junior engineer before they pick a language for the next extension. The README's micro-benchmark notes explain why rusqlite stayed slower than c-fast even after parser tweaks: each call pays for closure wrapping, Result handling , and a per-row Vec allocation . Swapping that Vec for a stack buffer only shaved a few nanoseconds on integer-only data. If your workload is mostly integers and you already ship Rust, the sqlite-loadable or rusqlite path may be good enough. When decimals dominate and binary size matters, c-fast/ is the documented winner on this VM. Federated spools linking vessels, metaphor for SQLite parse number feeding many analytic queries Image: Library illustration for data parsing pipelines; not from the research repo. Correctness checks you can rerun The repo ships three scripts: 1. test.py — 40 curated cases checking both value and typeof . 2. fuzz.py — random junk and extreme-precision decimals against a Python float reference; 4 seeds × 500k inputs × 4 extensions with no mismatches logged in results/fuzz.txt . 3. bench.py — reproduction for mixed , int , or real suites. Reproduction commands from the README: ./build.sh python test.py python fuzz.py 1 python bench.py mixed Or compile only the fast C path: gcc -O2 -fPIC -shared c-fast/parse number.c -o parse number.so For readers already using Willison's LLM CLI 0.37 https://explainx.ai/blog/llm-0-37-simon-willison-gpt-6-1-sol-terra-openai-compatible-endpoint-add-guide-2026 , this repo is the same author showing chat models doing native code iteration outside that tool's Python packaging. How this compares to agent harness workflows Willison did not open a repository in Claude Code or Codex . He iterated in chat , then asked the model to format results like his research series . That is closer to how product managers prototype SQL UDFs than to how teams run hooked agent loops on monorepos. Contrast with Battle Drones https://explainx.ai/blog/battle-drones-simon-willison-vibe-coded-rts-audio-multiplayer-claude-code-321-commits-2026 , where nearly all commits came from Claude Code across hundreds of iterations. The SQLite story shows narrow mechanical prompts still excel when: - The acceptance tests are numerical and fuzzable . - The deliverable is a self-contained directory , not a production service. - You can manually spot-check one .so file before merge. For teams standardized on coding agents , the migration path is straightforward: drop the research folder into a repo, wire build.sh into CI, and let agents extend parse number for locale-specific symbols under the same fuzz harness. Links to wider Claude and local-AI coverage This sits beside other November posts on Opus 5.5 capability and limits: - GDPx agentic benchmark scores https://explainx.ai/blog/semianalysis-gdpx-benchmark-opus-5-5-12-percent-agents-fail-2026 show frontier models still fail most long-horizon tasks even while they ace tight numeric coding loops like this one. - Weight-space editing experiments https://explainx.ai/blog/weight-space-programming-gpt-6-astra-edits-qwen3-5-0-8b-weights-math-7-9-to-48-1-mit-csail-2026 quote Willison's earlier work interpreting weights locally, a different axis of "model meets low-level code." - Our MLX Apple Silicon guide https://explainx.ai/blog/mlx-apple-silicon-run-llms-complete-guide-2026 covers local inference stacks; SQLite extensions pair naturally with on-device analytics when you do not want a server between CSV and chart. If you embed SQLite inside agent tools, remember loadable extensions are native code . Review them like any supply-chain binary . Sandboxing patterns from our agent sandbox comparison https://explainx.ai/blog/agent-sandboxes-coding-agents-compared-e2b-modal-daytona-guide-2026 still apply when an agent can LOAD EXTENSION from an untrusted path. Red-team report folder for validating SQLite parse number extension fuzz tests Image: Library illustration for correctness testing discipline. Limits and caveats - Locale: Both C paths ultimately depend on strtod on slow paths; README warns strtod respects the host locale unless the fast path handles the input. - Model authorship: The research README states LLM-generated text and code; human review still matters for security and maintenance. - Alpha Rust crate: sqlite-loadable is labeled alpha in the dependency table; expect API churn. - Not a SQLite release: Simon's work is third-party . Upstream SQLite docs remain the authority on extension APIs . - Chat versus Code: Reproducing this exact workflow needs Opus 5.5 access and manual copy-out; it is not an official Anthropic recipe. What people building with agents should try 1. Scope extensions narrowly. One scalar function with a written spec beats "build me a database engine." 2. Demand fuzz parity across language variants before picking a winner. 3. Measure net ns/row , not wall clock on toy tables. 4. Publish research folders so teammates diff prompts, not only binaries. 5. Promote winning code to CI once chat iteration stabilizes. Packaging for teammates who do not use Claude If you want others to review the work without an Anthropic login, mirror the simonw/research folder into your org git, pin the bench.py seed , and attach results/fuzz.txt to the pull request. Reviewers then argue about strtod fast paths and rusqlite closure overhead , not about chat transcripts. That habit transfers directly to agent pipelines that emit SQL against customer exports: the extension becomes a versioned artifact, while the prompt stays disposable. For embedding-heavy stacks that also load vectors beside SQLite, see how pgvector and peers compare https://explainx.ai/blog/vector-databases-rag-compared-pgvector-qdrant-chroma-weaviate-2026 when you outgrow single-file analytics. Related reading Benchmark figures and file sizes are taken from the simonw/research README as of November 8, 2026. Claude model availability and chat features may differ by account region and plan.