diesel-rs/diesel · error

Tuples should never implement `ToSql` directly

Error message

Tuples should never implement `ToSql` directly

What it means

Diesel's macro-generated `HasSqlType` implementation for tuples calls `unreachable!` in `metadata()` with this message (comment/branch nominally about tuple `ToSql`). Tuples are supported as row types only via `FromSqlRow`/`Queryable`, not as a directly serializable `ToSql` column type. Hitting this means code attempted to serialize a tuple as a single value.

Solutions

  1. Use `eq_any` with a Vec for membership tests instead of a tuple equality: `.filter(id.eq_any(vec![1, 2]))`.
  2. Bind each tuple element to its own column instead of the tuple as a whole.
  3. If you need a composite value, define a proper struct with diesel's `Queryable`/`Insertable` derives, or use `(a, b)` row patterns only where diesel supports row comparisons with a matching backend.
  4. Check that you aren't passing a tuple to a single-column insert value slot.

Example fix

// before
users.filter((id, name).eq((1, "a".to_string()))) // tuple used as single ToSql value
// after
users.filter(id.eq(1).and(name.eq("a")))
Defensive patterns

Strategy: type-guard

Validate before calling

// compile-time: don't pass tuples as single-column values
// bad: users.filter(id.eq((1, 2)))
// good: users.filter(id.eq_any(vec![1, 2]))

Type guard

// only tuples of ToSql fields used via row traits are valid; never call ToSql on a tuple
fn assert_single_column<V: diesel::serialize::ToSql<ST, DB>>(_: &V) {}
// example: assert_single_column(&user_id); // compiles; assert_single_column(&(1, 2)); // does not

Prevention

When it happens

Trigger: Using a tuple like `(a, b)` as a bind parameter / `ToSql` value in a query (e.g. `.eq((1,2))`, `.insert((1,2))` into a single column, or passing a tuple where a single-column value is expected); calling `ToSql` methods on a tuple type directly.

Common situations: Trying to compare a column against a tuple ('IN' misuse) instead of using `eq_any(vec![...])`; mixing up row tuples with column values in `insert_into(...).values(...)`; generated code/dsl misuse in custom backend code.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.


AI-assisted analysis of diesel-rs/diesel@6fa6ed01b2 (2026-09-07). Data as JSON: /api/errors/a61071b317c0b889. Report an issue: GitHub.

Appendix: source

Thrown at diesel/src/type_impls/tuples.rs:59

        $($tt)*
    };
}

macro_rules! tuple_impls {
    ($(
        $Tuple:tt {
            $(($idx:tt) -> $T:ident, $ST:ident, $TT:ident,)+
        }
    )+) => {
        $(
            fake_variadic!{
                $Tuple ->
                impl<$($T),+, __DB> HasSqlType<($($T,)+)> for __DB where
                    $(__DB: HasSqlType<$T>),+,
                    __DB: Backend,
                {
                    fn metadata(_: &mut __DB::MetadataLookup) -> __DB::TypeMetadata {
                        unreachable!("Tuples should never implement `ToSql` directly");
                    }
                }
            }

            impl_from_sql_row!(($($T,)+), ($($ST,)+));


            fake_variadic! {
                $Tuple ->
                #[diagnostic::do_not_recommend]
                impl<$($T: Expression),+> Expression for ($($T,)+)
                where ($($T::SqlType, )*): TypedExpressionType
                {
                    type SqlType = ($(<$T as Expression>::SqlType,)+);
                }
            }
            fake_variadic! {
                $Tuple -> impl<$($T: TypedExpressionType,)*> TypedExpressionType for ($($T,)*) {}

View on GitHub (pinned to 6fa6ed01b2)