sqlx icon indicating copy to clipboard operation
sqlx copied to clipboard

[Proposal] Support named bind parameters

Open mehcode opened this issue 5 years ago • 6 comments

struct Query {
  fn bind_as<T>(name: &'static str, value: T) -> Self;
}
query!("SELECT ?name", name = 320);

mehcode avatar Mar 30 '20 08:03 mehcode

With call-site hygiene stabilized in 1.45, the explicit binding could actually be omitted:

let name = 320;
query!("select ?name");

abonander avatar Jul 29 '20 22:07 abonander

Should this be closed in favor of #875? That one has a bunch more comments.

jplatte avatar Aug 21 '21 20:08 jplatte

This is a related issue of supporting named parameters in general. Implementing #875 would close both issues.

abonander avatar Aug 30 '21 20:08 abonander

Is there any update on named bindings?

CMeldgaard avatar Aug 29 '24 11:08 CMeldgaard

Not really, otherwise this issue would be closed, or that progress would be linked to this issue. You can however help make this happen.. In rust, most people are volunteers ^^ The contribution guide is here: https://github.com/launchbadge/sqlx/blob/main/CONTRIBUTING.md

In case your company needs this, you could also consider sponsoring @abonander (I don't speak for launchbadge or Austin => have no inside intel if that is an option, but that should likely be taken offline anyway..)

CommanderStorm avatar Aug 29 '24 15:08 CommanderStorm

I have come up with a solution to this issue. I wrote a procedural macro to wrap the sqlx command, the SQL string, and the parameters.

        let aString = "hello";
        let aNumber = 12;
        let aBoolean = true;

        let res = bind_by_name!(
            sqlx::query
            "select :aString as aString, :aNumber as aNumber, :aBoolean as aBoolean"
            [aString aNumber aBoolean]
        ).fetch_one(&db.pool).await?

This expands to:

sqlx::query("select ? as aString, ? as aNumber, ? as aBoolean")
  .bind(aString).bind(aNumber).bind(aBoolean)
  .fetch_one(&db.pool).await?

You can also map values directly:

        let res = bind_by_name!(
            sqlx::query
            "select :aString as aString, :aNumber as aNumber, :aBoolean as aBoolean"
            [aString:"hello" aNumber:12 aBoolean:true]
        ).fetch_one(&db.pool).await?

Whitespace and brackets are flexable

        let res = bind_by_name!(
            sqlx::query "select :aString as foo" {aString:"hello"}
       ).fetch_one(&db.pool).await?

This will work on any sqlx command that accepts a string and has a bind function.

There is also a postgres version that uses a$ prefix and has numbered placeholders.

        let res = bind_by_name_pg!(
            sqlx::query
            "select $aString as aString, $aNumber as aNumber, $aBoolean as aBoolean"
            [aString:"hello" aNumber:12 aBoolean:true]
        )

That expands to:

sqlx::query("select $1 as aString, $2 as aNumber, $3 as aBoolean")
  .bind("hello").bind(12).bind(true)

The macro shows edit-time errors if you use a parameter name in your SQL that is not in the map.

Screenshot 2024-10-04 at 10 40 01 PM Screenshot 2024-10-04 at 10 40 08 PM

As you can see, I am not a big fan of "noisy" syntax. 😎

I will post this crate after some feedback and if there is interest.

Working Screenshots

Screenshot 2024-10-04 at 10 50 43 PM Screenshot 2024-10-04 at 10 50 48 PM

Flamenco avatar Oct 05 '24 02:10 Flamenco

@Flamenco, i would love to see this as a crate :D

CMeldgaard avatar Oct 30 '24 14:10 CMeldgaard

@CMeldgaard This is still on the TODO list. It's going to take a bit more encouragement from the public before we publish. :D

Flamenco avatar Mar 17 '25 12:03 Flamenco

(I posted this in another issue already), in sea-query 1.0-rc you can do:

let values = vec![(Uuid::new_v4(), 12, "A"), (Uuid::new_v4(), 14, "B")];

let res = sea_query::sqlx::sqlite::query!(
    sql = r#"INSERT INTO "character" ("uuid", "font_size", "character") VALUES {..(values.0:2),}"#
).execute(pool).await?;

let character = Character { id: 1, font_size: 14 };

let res = sea_query::sqlx::sqlite::query!(
    sql = r#"UPDATE "character" SET "font_size" = {character.font_size} WHERE "id" = {character.id}"#
).execute(pool).await?;

let ids = [2, 3, 4];

let res = sea_query::sqlx::sqlite::query!(
    sql = r#"DELETE FROM "character" WHERE "id" IN ({..ids})"#
).execute(pool).await?;

full example can be found here: https://github.com/SeaQL/sea-query/blob/master/examples/sqlx_sqlite/src/main.rs

tyt2y3 avatar Aug 22 '25 22:08 tyt2y3