Skip to content
Open
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
27 changes: 14 additions & 13 deletions sqlx-sqlite/src/statement/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,19 +263,20 @@ impl StatementHandle {
return Err(SqliteError::new(self.db_handle()).into());
}

let datatype = CStr::from_ptr(datatype);

Ok(
if primary_key != 0
&& datatype
.to_bytes()
.eq_ignore_ascii_case("integer".as_bytes())
{
None
} else {
Some(not_null == 0)
},
)
// `sqlite3_table_column_metadata()` sets the declared type to NULL for a column
// declared without one, e.g. `CREATE TABLE foo (bar PRIMARY KEY)`. Such a column
// has no type name to compare against, and is by definition not declared
// `INTEGER`, so it cannot be a rowid alias.
let is_integer = !datatype.is_null()
&& CStr::from_ptr(datatype)
.to_bytes()
.eq_ignore_ascii_case("integer".as_bytes());

Ok(if primary_key != 0 && is_integer {
None
} else {
Some(not_null == 0)
})
}
}

Expand Down
35 changes: 34 additions & 1 deletion tests/sqlite/describe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use sqlx::error::DatabaseError;
use sqlx::sqlite::{SqliteConnectOptions, SqliteError};
use sqlx::TypeInfo;
use sqlx::{sqlite::Sqlite, Column, Executor};
use sqlx::{ConnectOptions, SqlSafeStr};
use sqlx::{ConnectOptions, Connection, SqlSafeStr, SqliteConnection};
use sqlx_test::new;
use std::env;

Expand Down Expand Up @@ -1095,3 +1095,36 @@ async fn it_describes_analytical_function() -> anyhow::Result<()> {

Ok(())
}

// A column declared with no type at all, e.g. `CREATE TABLE foo (bar PRIMARY KEY)`, is
// legal SQLite. `sqlite3_table_column_metadata()` reports a NULL declared type for such a
// column, which `column_nullable()` used to dereference unconditionally, segfaulting the
// process, and thus rustc itself when the `query!()` macros describe a live database.
#[sqlx_macros::test]
async fn it_describes_columns_with_no_declared_type() -> anyhow::Result<()> {
let mut conn = SqliteConnection::connect(":memory:").await?;

conn.execute(
r#"
CREATE TABLE untyped (
id PRIMARY KEY,
typeless,
typed TEXT NOT NULL
);
"#
.into_sql_str(),
)
.await?;

let d = conn
.describe("SELECT id, typeless, typed FROM untyped".into_sql_str())
.await?;

// A `PRIMARY KEY` with no declared type is not an `INTEGER PRIMARY KEY`, so it is not a
// rowid alias and SQLite does permit NULLs in it.
assert_eq!(d.nullable(0), Some(true));
assert_eq!(d.nullable(1), Some(true));
assert_eq!(d.nullable(2), Some(false));

Ok(())
}
Loading