Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ proc-macro2 = "1"
quote = "1"
serde = { version = "1", features = ["derive"] }
serde_json = { workspace = true }
syn = { version = "2", features = ["full"] }
syn = { version = "2", features = ["full", "visit-mut"] }

[dev-dependencies]
insta = { version = "1", features = ["filters"] }
Expand Down
51 changes: 50 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ All options are passed in `codegen[*].options`:
| Key | Type | Default | Description |
|---|---|---|---|
| `output` | string | `queries.rs` | Output filename |
| `overrides` | array | `[]` | Type overrides (see below) |
| `overrides` | array | `[]` | Type overrides (`rs_type`, optional `borrowed_rs_type`; see below) |
| `row_derives` | array | `[]` | Extra derives for row and params structs |
| `enum_derives` | array | `[]` | Extra derives for generated enum types |
| `composite_derives` | array | `[]` | Extra derives for generated composite types |
Expand All @@ -77,6 +77,55 @@ options:
copy_cheap: false
```

### Borrowed parameters

Add `borrowed_rs_type` to a type or column override to take that type by
reference in parameter positions. Row struct fields, array contents, and the
`Item` of `:copyfrom` chunks continue to use the owned form:

```yaml
options:
overrides:
- db_type: "text"
borrowed_rs_type: "&str"
```

With that override, generated signatures borrow scalar `text` parameters and
the codegen threads lifetimes only where needed:

```rust
// Scalar — lifetime elided
pub async fn get_author_by_name<E: AsExecutor>(
mut db: E, name: &str,
) -> Result<GetAuthorByNameRow, sqlx::Error> { ... }

// Multiple params — struct carries `'a`, fn uses `'_`
pub struct CreateAuthorParams<'a> {
pub name: &'a str,
pub bio: Option<&'a str>,
}
pub async fn create_author<E: AsExecutor>(
mut db: E, arg: CreateAuthorParams<'_>,
) -> Result<CreateAuthorRow, sqlx::Error> { ... }

// Row struct stays owned — results are returned by value
pub struct GetAuthorByNameRow { pub name: String, /* ... */ }
```

`rs_type` is optional alongside `borrowed_rs_type`. Omit it to keep the
built-in owned default; set both to fully customize:

```yaml
overrides:
- db_type: "text"
rs_type: "MyStr" # used for row fields & array contents
borrowed_rs_type: "&MyStr" # used for scalar params
```

For `text[]` and `sqlc.slice(text)` the wrapper becomes a borrowed slice while
the inner item stays owned (`&[String]`), so callers can pass `&my_vec`
directly without re-collecting.

## Supported PostgreSQL types

| PostgreSQL | Rust |
Expand Down
2 changes: 1 addition & 1 deletion examples/advanced-types/src/queries.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Code generated by sqlc-gen-sqlx v0.1.7. DO NOT EDIT.
// Code generated by sqlc-gen-sqlx v0.2.0. DO NOT EDIT.
// sqlc version: v1.30.0

#![allow(
Expand Down
2 changes: 1 addition & 1 deletion examples/basic/src/queries.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Code generated by sqlc-gen-sqlx v0.1.7. DO NOT EDIT.
// Code generated by sqlc-gen-sqlx v0.2.0. DO NOT EDIT.
// sqlc version: v1.30.0

#![allow(
Expand Down
2 changes: 1 addition & 1 deletion examples/batch/src/queries.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Code generated by sqlc-gen-sqlx v0.1.7. DO NOT EDIT.
// Code generated by sqlc-gen-sqlx v0.2.0. DO NOT EDIT.
// sqlc version: v1.30.0

#![allow(
Expand Down
8 changes: 8 additions & 0 deletions examples/borrowed/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "borrowed"
version = "0.1.0"
edition = "2024"

[dependencies]
sqlx = { workspace = true }
tokio = { workspace = true }
14 changes: 14 additions & 0 deletions examples/borrowed/queries.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- name: GetAuthor :one
SELECT id, name, bio FROM authors WHERE id = $1;

-- name: GetAuthorByName :one
SELECT id, name, bio FROM authors WHERE name = $1;

-- name: ListAuthors :many
SELECT id, name, bio FROM authors ORDER BY name;

-- name: CreateAuthor :one
INSERT INTO authors (name, bio) VALUES ($1, $2) RETURNING id, name, bio;

-- name: DeleteAuthor :exec
DELETE FROM authors WHERE id = $1;
5 changes: 5 additions & 0 deletions examples/borrowed/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE authors (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
bio TEXT
);
51 changes: 51 additions & 0 deletions examples/borrowed/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#[cfg(test)]
use sqlx::{Connection as _, PgConnection};

#[path = "queries.rs"]
#[cfg(test)]
mod queries;
#[cfg(test)]
use queries::CreateAuthorParams;

#[cfg(test)]
#[tokio::test]
async fn test_borrowed_author_roundtrip() {
let db_url = std::env::var("DATABASE_URL")
.unwrap_or_else(|_| "postgres://sqlc:sqlc@localhost:5432/sqlc_test".to_string());
let mut conn = PgConnection::connect(&db_url).await.expect("connect");

sqlx::query("CREATE TABLE IF NOT EXISTS authors (id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL, bio TEXT)")
.execute(&mut conn)
.await
.unwrap();
sqlx::query("TRUNCATE authors RESTART IDENTITY CASCADE")
.execute(&mut conn)
.await
.unwrap();

// Borrow string literals directly — no allocation needed.
let author = queries::create_author(
&mut conn,
CreateAuthorParams {
name: "Alice",
bio: Some("Loves Rust"),
},
)
.await
.expect("create");
assert_eq!(author.name, "Alice");
assert_eq!(author.bio.as_deref(), Some("Loves Rust"));

// Borrow an owned String the caller already has.
let stored_name: String = author.name.clone();
let fetched = queries::get_author_by_name(&mut conn, &stored_name)
.await
.expect("get_by_name");
assert_eq!(fetched.id, author.id);

queries::delete_author(&mut conn, author.id)
.await
.expect("delete");
}

fn main() {}
113 changes: 113 additions & 0 deletions examples/borrowed/src/queries.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Code generated by sqlc-gen-sqlx v0.2.0. DO NOT EDIT.
// sqlc version: v1.30.0

#![allow(
dead_code,
reason = "generated queries may expose items a caller does not use"
)]

pub trait AsExecutor {
fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres>;
}
impl AsExecutor for sqlx::PgPool {
fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres> {
&*self
}
}
impl AsExecutor for &sqlx::PgPool {
fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres> {
*self
}
}
impl AsExecutor for sqlx::PgConnection {
fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres> {
&mut *self
}
}
impl AsExecutor for sqlx::Transaction<'_, sqlx::Postgres> {
fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres> {
&mut **self
}
}
impl AsExecutor for sqlx::pool::PoolConnection<sqlx::Postgres> {
fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres> {
&mut **self
}
}
impl<T: AsExecutor + ?Sized> AsExecutor for &mut T {
fn as_executor(&mut self) -> impl sqlx::Executor<'_, Database = sqlx::Postgres> {
(**self).as_executor()
}
}
const GET_AUTHOR: &str = "SELECT id, name, bio FROM authors WHERE id = $1";
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct GetAuthorRow {
pub id: i64,
pub name: String,
pub bio: Option<String>,
}
pub async fn get_author<E: AsExecutor>(mut db: E, id: i64) -> Result<GetAuthorRow, sqlx::Error> {
sqlx::query_as::<_, GetAuthorRow>(GET_AUTHOR)
.bind(id)
.fetch_one(db.as_executor())
.await
}
const GET_AUTHOR_BY_NAME: &str = "SELECT id, name, bio FROM authors WHERE name = $1";
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct GetAuthorByNameRow {
pub id: i64,
pub name: String,
pub bio: Option<String>,
}
pub async fn get_author_by_name<E: AsExecutor>(
mut db: E,
name: &str,
) -> Result<GetAuthorByNameRow, sqlx::Error> {
sqlx::query_as::<_, GetAuthorByNameRow>(GET_AUTHOR_BY_NAME)
.bind(name)
.fetch_one(db.as_executor())
.await
}
const LIST_AUTHORS: &str = "SELECT id, name, bio FROM authors ORDER BY name";
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct ListAuthorsRow {
pub id: i64,
pub name: String,
pub bio: Option<String>,
}
pub async fn list_authors<E: AsExecutor>(mut db: E) -> Result<Vec<ListAuthorsRow>, sqlx::Error> {
sqlx::query_as::<_, ListAuthorsRow>(LIST_AUTHORS)
.fetch_all(db.as_executor())
.await
}
#[derive(Debug, Clone)]
pub struct CreateAuthorParams<'a> {
pub name: &'a str,
pub bio: Option<&'a str>,
}
const CREATE_AUTHOR: &str =
"INSERT INTO authors (name, bio) VALUES ($1, $2) RETURNING id, name, bio";
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct CreateAuthorRow {
pub id: i64,
pub name: String,
pub bio: Option<String>,
}
pub async fn create_author<E: AsExecutor>(
mut db: E,
arg: CreateAuthorParams<'_>,
) -> Result<CreateAuthorRow, sqlx::Error> {
sqlx::query_as::<_, CreateAuthorRow>(CREATE_AUTHOR)
.bind(arg.name)
.bind(arg.bio)
.fetch_one(db.as_executor())
.await
}
const DELETE_AUTHOR: &str = "DELETE FROM authors WHERE id = $1";
pub async fn delete_author<E: AsExecutor>(mut db: E, id: i64) -> Result<(), sqlx::Error> {
sqlx::query(DELETE_AUTHOR)
.bind(id)
.execute(db.as_executor())
.await?;
Ok(())
}
2 changes: 1 addition & 1 deletion examples/enums/src/queries.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Code generated by sqlc-gen-sqlx v0.1.7. DO NOT EDIT.
// Code generated by sqlc-gen-sqlx v0.2.0. DO NOT EDIT.
// sqlc version: v1.30.0

#![allow(
Expand Down
14 changes: 13 additions & 1 deletion sqlc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ plugins:
- name: sqlc-gen-sqlx
wasm:
url: file://target/wasm32-wasip1/debug/sqlc-gen-sqlx.wasm
sha256: "87780a1e4ebe3f7bfb3fdd4041e8f7127b5c35d540c40b456558d87f1ef62e5b"
sha256: "0405bc13685568ff34c6ff8fb43fc4fe474916b1175007c31b2485c99a6ee2a7"

sql:
- schema: examples/basic/schema.sql
Expand Down Expand Up @@ -41,3 +41,15 @@ sql:
out: examples/enums/src
options:
output: queries.rs

- schema: examples/borrowed/schema.sql
queries: examples/borrowed/queries.sql
engine: postgresql
codegen:
- plugin: sqlc-gen-sqlx
out: examples/borrowed/src
options:
output: queries.rs
overrides:
- db_type: "text"
borrowed_rs_type: "&str"
Loading
Loading