You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When using the non-macro query_as functions, and deriving FromRow, the code always sets default value for the returned value if it is null and the field in the struct is not an Option. This is surprising, I would expect an Err return if a column is null and trying to be forced into a struct without an Option. I do not see this documented. I know that these functions are not the "normal" usage (macros preferred for sqlx) but I would still expect them to work similar to serde_json if a field is not found and the struct field is not an Option, i.e. return an Err.
Minimal Reproduction
src/main.rs
use sqlx::SqlitePool;#[derive(sqlx::FromRow)]structTodo{id:i64,description:String,done:bool,}#[derive(sqlx::FromRow)]structTodoOption{id:i64,description:String,done:Option<bool>,}#[tokio::main(flavor = "current_thread")]asyncfnmain() -> anyhow::Result<()>{let pool = SqlitePool::connect("sqlite::memory:").await?;// read migrations from the filesystem into a stringlet migration = include_str!("../migrations/todos.sql");// Run migration on start
sqlx::query(&migration).execute(&pool).await?;println!();list_todos(&pool).await?;Ok(())}asyncfnlist_todos(pool:&SqlitePool) -> anyhow::Result<()>{println!("Printing value of todo.done using Todo struct (no Option for nullable done column)");let query_str = r#"SELECT id, description, doneFROM todosORDER BY id "#;let rec:Todo = sqlx::query_as(&query_str).fetch_one(pool).await?;println!("Done value: {:?}", rec.done);println!();println!("Printing value of todo.done using TodoOption struct (with Option for nullable done column)");let rec:TodoOption = sqlx::query_as(&query_str).fetch_one(pool).await?;println!("Done value: {:?}", rec.done);Ok(())}
migrations/todo.sql
CREATETABLEIF NOT EXISTS todos
(
id INTEGERPRIMARY KEYNOT NULL,
description TEXTNOT NULL,
done BOOLEAN DEFAULT NULL
);
INSERT INTO todos (description) VALUES ('done is null');
cargo.toml
[package]
name = "sqlx_demo"version = "0.1.0"edition = "2021"
[dependencies]
anyhow = "1.0"futures = "0.3"sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio-native-tls"] }
tokio = { version = "1.20.0", features = ["rt", "macros"]}
Output
Printing value of todo.done using Todo struct (no Option for nullable done column)
Done value: false
Printing value of todo.done using TodoOption struct (with Option for nullable done column)
Done value: None
Info
SQLx version: 0.7.4
SQLx features enabled: "sqlite", "runtime-tokio-native-tls"
Database server and version: SQLite 3.43.2
Operating system: Mac OS 14.4.1
rustc --version: 1.77.2 (25ef9e3d8 2024-04-09)
The text was updated successfully, but these errors were encountered:
msberk
changed the title
Null values for ints in SQLite are being converted to default values without error
Null values in SQLite are being converted to default values without error or default attribute
May 1, 2024
Normally, we don't explicitly implement type conversions, because that should be handled in the database itself. For the MySQL or Postgres driver, this would be an error. However, since the SQLite API functions implement this conversion internally, it's actually more work for us to prevent the conversion, so it was just omitted.
If you use the checked Row::get() or ::try_get() methods, this will panic or error, respectively, but the FromRow derive uses ::try_get_unchecked() to avoid an extra branch on decoding the value. This also allows conversion between types that are binary-compatible but not considered compatible in the API.
I'm not particularly married to any of these decisions, but there's a high likelihood that there are people out there who depend on the current behavior.
Oof, fair enough. I didn’t know this was inherent behavior of SQLite, and I think it really caught me off guard since the behavior is a significant departure from how most other Rust deserializations I have used work and figured since the SQLx macros disallowed the mismatch it was an unintended inconsistency.
I’m not going to fight to have this changed though I might ask that the docs are updated to specifically note that FromRow derivations when using SQLite will not Err if a NULL value on a required field is encountered. That would be helpful, I got spun around on this for a few hours.
Bug Description
When using the non-macro
query_as
functions, and derivingFromRow
, the code always sets default value for the returned value if it is null and the field in the struct is not anOption
. This is surprising, I would expect anErr
return if a column is null and trying to be forced into a struct without anOption
. I do not see this documented. I know that these functions are not the "normal" usage (macros preferred forsqlx
) but I would still expect them to work similar toserde_json
if a field is not found and the struct field is not anOption
, i.e. return anErr
.Minimal Reproduction
src/main.rs
migrations/todo.sql
cargo.toml
Output
Info
rustc --version
: 1.77.2 (25ef9e3d8 2024-04-09)The text was updated successfully, but these errors were encountered: