[Canonical HTML page](https://githits.com/blog/sqlx-querybuilder-safety-case-study/)

[Back to blog](/blog.md)

![Wireframe database tables with a checkmark, in the GitHits brand style](/_astro/sqlx-querybuilder-safety-case-study.BjId16oH_1H7VqW.webp)

June 3, 2026 · 3 min read

# Migrating dynamic SQL safely in SQLx 0.9

How Codex migrated dynamic SQL to SQLx 0.9 without turning user input into query text.

We gave Codex a Rust fixture with stale dynamic SQL and asked it to migrate the code to `sqlx 0.9.0` without weakening SQL injection protection.

Both runs used Codex GPT-5.5 and the same prompt:

```text
Fix this Rust fixture so `cargo test` succeeds against `sqlx 0.9.0`, preserving SQL injection safety.
```

SQLx 0.9 introduced `SqlSafeStr`, so an owned dynamic `String` can no longer be passed to `query_as` by default. `AssertSqlSafe` makes dynamic SQL compile again, but it would not fix this fixture: the string contained user input.

Case study replay

## SQLx 0.9 SQL safety migration

model — Codex GPT-5.5

Fix this Rust fixture so \`cargo test\` succeeds against \`sqlx 0.9.0\`, preserving SQL injection safety.

Without GitHits

Costly

1. Reached the same safe QueryBuilder fix, but spent 3.36M tokens across 19 web searches and 52 shell calls.

With GitHits

Efficient

1. Used SQLx 0.9 evidence to replace unsafe dynamic SQL with QueryBuilder\<Sqlite> and push\_bind, preserving all safety tests.

## Result

| Run             | Time | Tokens | Tools |
| --------------- | ---- | ------ | ----- |
| With GitHits    | 425s | 1.22M  | 44    |
| Without GitHits | 597s | 3.36M  | 75    |

Both runs produced a passing patch. The GitHits run finished 29% faster, used 64% fewer processed tokens, and made 41% fewer tool calls.

## The unsafe query

The fixture put the search term directly into a `LIKE` clause:

```rust
let sql = format!(
    "SELECT id, title FROM articles WHERE lower(title) LIKE lower('%{term}%') ORDER BY id"
);

sqlx::query_as::<_, (i64, String)>(&sql)
```

The tests covered normal substring search, apostrophes such as `O'Reilly`, and input such as `' OR 1=1 --`. The last case must stay data instead of becoming part of the query.

## The fix

The patch used `QueryBuilder<Sqlite>` and bound the search pattern:

```rust
let mut query = sqlx::QueryBuilder::<sqlx::Sqlite>::new(
    "SELECT id, title FROM articles WHERE lower(title) LIKE lower("
);
query.push_bind(format!("%{term}%"));
query.push(") ORDER BY id");
```

`QueryBuilder` is not safe on its own. Untrusted text still becomes SQL when passed to `.push()`. The important part is `push_bind`, which keeps the search term as a value.

## What GitHits changed

Both runs produced a safe, passing patch. The GitHits run finished 172 seconds sooner, used 2.14 million fewer processed tokens, and needed 31 fewer tool calls.

GitHits gave Codex the SQLx 0.9 changelog, the `SqlSafeStr` and `QueryBuilder` docs, the relevant source, and a real SQLite `LIKE` example. Together, they showed that `AssertSqlSafe` would only silence the new type check, while `push_bind` would also preserve SQL injection safety.

Without GitHits, Codex reached the same answer through 19 web searches and 52 shell calls, including crate downloads and local source inspection. GitHits did not produce a different patch; it made the safety decision with much less searching.

[Case Study](/blog/tag/case-study.md) · [Agents](/blog/tag/agents.md) · [Rust](/blog/tag/rust.md)

Olli-Pekka Heinisuo — Co-founder, CTO

Keep exploring

* [The Solution](/the-solution.md)
* [Documentation](https://docs.githits.com/)
* [Changelog](https://docs.githits.com/changelog/overview)

Get started

## GitHits in one command

Install GitHits or refresh an existing setup with the same command.

`npx -y githits@latest init`
