sqlx icon indicating copy to clipboard operation
sqlx copied to clipboard

Sqlite mismatched types error

Open mrmykey opened this issue 2 years ago • 8 comments

sqlx 6.1 fails when running SQLite returning query with a float which ends with 0. Works with 1.1, fails with 1.0

// CREATE TABLE data_table(id INTEGER PRIMARY KEY AUTOINCREMENT, number REAL);
#[derive(sqlx::FromRow)]
struct DataTable { id: i64, number: f64 }

#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
    let pool = SqlitePool::connect("sqlite:app.db").await?;
    let sql = "insert into data_table (number) values (?) returning id, number";
    let _result = sqlx::query_as::<_, DataTable>(sql).bind(1.0).fetch_one(&pool).await?;
    Ok(())
}

Error: ColumnDecode { index: "\"number\"", source: "mismatched types; Rust type `f64` (as SQL type `REAL`) is not compatible with SQL type `INTEGER`" }

mrmykey avatar Sep 13 '22 16:09 mrmykey

This looks to be the same as #1596 which I thought was fixed already, but I am able to reproduce this.

abonander avatar Sep 13 '22 21:09 abonander

Added a followup to my original bug report on the SQLite forum: https://sqlite.org/forum/forumpost/712251ee03

abonander avatar Sep 13 '22 22:09 abonander

I think I've got bitten by this one too:

"mismatched types; Rust type `f32` (as SQL type `REAL`) is not compatible with SQL type `INTEGER`"

after inserting 5.0 with RETURNING *.

Last post on https://sqlite.org/forum/forumpost/712251ee03 is from 2022-09-13 with no response, am I doing something wrong?

sqlx 0.6 sqlite3 3.39.5

predmijat avatar Jan 15 '23 23:01 predmijat

Just hit this bug using latest sqlx :(

drager avatar Mar 05 '23 19:03 drager

I create a wapper of f64 to avoid this problem temporarily:

use sqlx::Sqlite;
use std::fmt::{self, Debug, Display};

#[derive(Clone, Copy, PartialEq)]
pub struct Float(pub f64);

impl Debug for Float {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(&self.0, f)
    }
}

impl Display for Float {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(&self.0, f)
    }
}

impl sqlx::Type<Sqlite> for Float {
    fn type_info() -> <Sqlite as sqlx::Database>::TypeInfo {
        <f64 as sqlx::Type<Sqlite>>::type_info()
    }

    fn compatible(ty: &<Sqlite as sqlx::Database>::TypeInfo) -> bool {
        use sqlx::TypeInfo;

        match ty.name() {
            "INTEGER" => true,
            _ => <f64 as sqlx::Type<Sqlite>>::compatible(ty),
        }
    }
}

impl<'q> sqlx::Encode<'q, Sqlite> for Float {
    fn encode_by_ref(
        &self,
        args: &mut <Sqlite as sqlx::database::HasArguments<'q>>::ArgumentBuffer,
    ) -> sqlx::encode::IsNull {
        args.push(sqlx::sqlite::SqliteArgumentValue::Double(self.0));
        sqlx::encode::IsNull::No
    }
}

impl<'r> sqlx::Decode<'r, Sqlite> for Float {
    fn decode(
        value: <Sqlite as sqlx::database::HasValueRef<'r>>::ValueRef,
    ) -> Result<Self, sqlx::error::BoxDynError> {
        use sqlx::{TypeInfo, ValueRef};

        match value.type_info().name() {
            "INTEGER" => Ok(Self(
                <i32 as sqlx::Decode<'_, Sqlite>>::decode(value)? as f64
            )),
            _ => Ok(Self(<f64 as sqlx::Decode<'_, Sqlite>>::decode(value)?)),
        }
    }
}

impl serde::ser::Serialize for Float {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_f64(self.0)
    }
}

fortunewang avatar Aug 16 '23 03:08 fortunewang

An easier workaround I got working is returning the number as real explicitly from sqlite:

insert into data_table (number) values (?) returning id, cast(number as real) as number

pbalaga avatar Aug 27 '23 19:08 pbalaga

This should be fixed in SQLite itself now: https://sqlite.org/src/info/4711fb69547f4f17

abonander avatar Apr 08 '24 10:04 abonander

Actually I just saw my own comment suggesting that this may have regressed.

abonander avatar Apr 08 '24 10:04 abonander