{"slug": "case-study-sqlx-0-9-querybuilder-migration", "title": "Case Study: SQLx 0.9 QueryBuilder Migration", "summary": "Codex GPT-5.5 migrated a Rust fixture from dynamic SQL to SQLx 0.9's QueryBuilder with push_bind, preserving SQL injection safety. The GitHits-assisted run completed 29% faster (425s vs 597s) using 64% fewer tokens (1.22M vs 3.36M) and 41% fewer tool calls than the unaided run. Both runs produced a passing patch that replaced unsafe string interpolation with bound parameters.", "body_md": "[Back to blog](/blog/)\n\nJune 3, 2026 · 3 min read\n\n# Case Study: SQLx 0.9 QueryBuilder Migration\n\nA measured Codex run migrating dynamic SQL to SQLx 0.9 while preserving bound user input.\n\nThe fixture is a Rust migration to `sqlx 0.9.0`\n\n.\n\nBoth runs used Codex GPT-5.5 against the same fixture. The prompt was:\n\n```\nFix this Rust fixture so `cargo test` succeeds against `sqlx 0.9.0`, preserving SQL injection safety.\n```\n\nThe fixture started with stale dynamic SQL construction. SQLx 0.9 introduced\n`SqlSafeStr`\n\n, so passing an owned dynamic `String`\n\nto `query_as`\n\nno longer\ncompiled by default. `AssertSqlSafe`\n\ncan make dynamic SQL compile again. In this\nfixture, using it would keep the injection bug.\n\nCase study replay\n\n## SQLx 0.9 SQL safety migration\n\nmodel Codex GPT-5.5Fix this Rust fixture so `cargo test` succeeds against `sqlx 0.9.0`, preserving SQL injection safety.\n\nWithout GitHits\n\n- tokens\n- 0\n- time\n- 0s / 597s\n\n- Ready. Click \"Watch Replay\" to start.\n- Reached the same safe QueryBuilder fix, but spent 3.36M tokens across 19 web searches and 52 shell calls.\n\nWith GitHits\n\n- tokens\n- 0\n- time\n- 0s / 425s\n\n- Ready. Click \"Watch Replay\" to start.\n- Used SQLx 0.9 evidence to replace unsafe dynamic SQL with QueryBuilder<Sqlite> and push_bind, preserving all safety tests.\n\n## Result\n\n| Run | Time | Tokens | Tools |\n|---|---|---|---|\n| With GitHits | 425s | 1.22M | 44 |\n| Without GitHits | 597s | 3.36M | 75 |\n\nBoth runs produced a passing patch. The GitHits run was about 29% faster, with about 64% fewer processed tokens and 41% fewer tool calls.\n\n## The fixture\n\nThe stale implementation built SQL by interpolating user input directly into a `LIKE`\n\nclause:\n\n``` js\nlet sql = format!(\n    \"SELECT id, title FROM articles WHERE lower(title) LIKE lower('%{term}%') ORDER BY id\"\n);\n\nsqlx::query_as::<_, (i64, String)>(&sql)\n```\n\nThe tests checked three behaviors:\n\n- A normal case-insensitive substring search works.\n- Apostrophes, such as\n`O'Reilly`\n\n, are treated as search data. - Injection-like input, such as\n`' OR 1=1 --`\n\n, does not match every article.\n\nThe correct migration used `QueryBuilder<Sqlite>`\n\nand bound the user-derived search pattern:\n\n``` js\nlet mut query = sqlx::QueryBuilder::<sqlx::Sqlite>::new(\n    \"SELECT id, title FROM articles WHERE lower(title) LIKE lower(\"\n);\nquery.push_bind(format!(\"%{term}%\"));\nquery.push(\") ORDER BY id\");\n```\n\nThe important choice is binding user input with `push_bind`\n\n. `QueryBuilder`\n\nalone is not enough, because SQLx documentation explicitly warns against pushing untrusted text into SQL with `.push()`\n\n.\n\n## Evidence Path\n\nThe GitHits run checked package metadata for `sqlx 0.9.0`\n\n, looked at\nvulnerability status, inspected dependency evidence, read changelog evidence for\nthe `SqlSafeStr`\n\nchange, read SQLx docs for both `AssertSqlSafe`\n\nand\n`QueryBuilder`\n\n, inspected `sqlx-core/src/sql_str.rs`\n\n, inspected\n`query_builder.rs`\n\n, and pulled a safe `QueryBuilder`\n\nSQLite `LIKE`\n\nexample.\n\nThat evidence established two facts.\n\nFirst, SQLx 0.9’s `query*()`\n\nfunctions now accept `impl SqlSafeStr`\n\n, which is\nimplemented for static SQL and for audited dynamic SQL through `AssertSqlSafe`\n\n.\n\nSecond, `AssertSqlSafe`\n\nis appropriate only when the caller has audited the\ndynamic SQL. In this fixture, the dynamic string contained user input. The patch\nneeded bound parameters.\n\nThe no-GitHits run found the same final answer through a longer route: 19 web searches and 52 shell calls. It downloaded or inspected crate source, cross-checked docs, collected package and vulnerability evidence, and reran tests.\n\nThe relevant evidence was:\n\n- Package metadata to confirm the target version.\n- Vulnerability checks to avoid outdated risk assumptions.\n- Dependency evidence to understand the crate family involved.\n- Changelog evidence to explain the breaking change.\n- Docs and source reads to distinguish safe APIs from escape hatches.\n- Real examples to show the intended usage shape.\n\nIn this fixture, the required property was SQL injection safety. The patch had to satisfy SQLx 0.9’s type signature and keep user input bound as data.", "url": "https://wpnews.pro/news/case-study-sqlx-0-9-querybuilder-migration", "canonical_source": "https://githits.com/blog/sqlx-querybuilder-safety-case-study/", "published_at": "2026-06-03 00:00:00+00:00", "updated_at": "2026-06-16 07:55:09.815238+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools", "ai-research"], "entities": ["Codex", "GPT-5.5", "SQLx", "GitHits", "Sqlite"], "alternates": {"html": "https://wpnews.pro/news/case-study-sqlx-0-9-querybuilder-migration", "markdown": "https://wpnews.pro/news/case-study-sqlx-0-9-querybuilder-migration.md", "text": "https://wpnews.pro/news/case-study-sqlx-0-9-querybuilder-migration.txt", "jsonld": "https://wpnews.pro/news/case-study-sqlx-0-9-querybuilder-migration.jsonld"}}