mirror of
https://github.com/launchbadge/sqlx
synced 2024-09-20 14:21:57 +00:00
SQLite: implement column nullability checking
This commit is contained in:
parent
fe00c0d619
commit
4ffa7f0e01
3 changed files with 80 additions and 7 deletions
|
@ -163,7 +163,7 @@ impl Executor for SqliteConnection {
|
|||
|
||||
columns.push(Column {
|
||||
name: Some(name.into()),
|
||||
non_null: None,
|
||||
non_null: statement.column_not_null(i)?,
|
||||
table_id: None,
|
||||
type_info: r#type.map(|r#type| SqliteTypeInfo {
|
||||
r#type,
|
||||
|
|
|
@ -5,19 +5,23 @@ use core::ptr::{null, null_mut, NonNull};
|
|||
use std::collections::HashMap;
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_int;
|
||||
use std::ptr;
|
||||
|
||||
use libsqlite3_sys::{
|
||||
sqlite3_bind_parameter_count, sqlite3_clear_bindings, sqlite3_column_count,
|
||||
sqlite3_column_decltype, sqlite3_column_name, sqlite3_data_count, sqlite3_finalize,
|
||||
sqlite3_prepare_v3, sqlite3_reset, sqlite3_step, sqlite3_stmt, SQLITE_DONE, SQLITE_OK,
|
||||
SQLITE_PREPARE_NO_VTAB, SQLITE_PREPARE_PERSISTENT, SQLITE_ROW,
|
||||
sqlite3_column_database_name, sqlite3_column_decltype, sqlite3_column_name,
|
||||
sqlite3_column_origin_name, sqlite3_column_table_name, sqlite3_data_count, sqlite3_finalize,
|
||||
sqlite3_prepare_v3, sqlite3_reset, sqlite3_step, sqlite3_stmt, sqlite3_table_column_metadata,
|
||||
SQLITE_DONE, SQLITE_OK, SQLITE_PREPARE_NO_VTAB, SQLITE_PREPARE_PERSISTENT, SQLITE_ROW,
|
||||
};
|
||||
|
||||
use crate::sqlite::connection::SqliteConnectionHandle;
|
||||
use crate::sqlite::worker::Worker;
|
||||
|
||||
use crate::error::DatabaseError;
|
||||
use crate::sqlite::SqliteError;
|
||||
use crate::sqlite::{SqliteArguments, SqliteConnection};
|
||||
use bitflags::_core::str::from_utf8_unchecked;
|
||||
|
||||
/// Return values from [SqliteStatement::step].
|
||||
pub(super) enum Step {
|
||||
|
@ -162,6 +166,55 @@ impl Statement {
|
|||
name.map(|s| s.to_str().unwrap())
|
||||
}
|
||||
|
||||
pub(super) fn column_not_null(&mut self, index: usize) -> crate::Result<Option<bool>> {
|
||||
unsafe {
|
||||
// https://sqlite.org/c3ref/column_database_name.html
|
||||
//
|
||||
// ### Note
|
||||
// The returned string is valid until the prepared statement is destroyed using
|
||||
// sqlite3_finalize() or until the statement is automatically reprepared by the
|
||||
// first call to sqlite3_step() for a particular run or until the same information
|
||||
// is requested again in a different encoding.
|
||||
let db_name = sqlite3_column_database_name(self.handle(), index as c_int);
|
||||
let table_name = sqlite3_column_table_name(self.handle(), index as c_int);
|
||||
let origin_name = sqlite3_column_origin_name(self.handle(), index as c_int);
|
||||
|
||||
if db_name.is_null() || table_name.is_null() || origin_name.is_null() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut not_null: c_int = 0;
|
||||
|
||||
// https://sqlite.org/c3ref/table_column_metadata.html
|
||||
let status = sqlite3_table_column_metadata(
|
||||
self.connection.0.as_ptr(),
|
||||
db_name,
|
||||
table_name,
|
||||
origin_name,
|
||||
// function docs state to provide NULL for return values you don't care about
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
&mut not_null,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
|
||||
if status != SQLITE_OK {
|
||||
// implementation note: the docs for sqlite3_table_column_metadata() specify
|
||||
// that an error can be returned if the column came from a view; however,
|
||||
// experimentally we found that the above functions give us the true origin
|
||||
// for columns in views that came from real tables and so we should never hit this
|
||||
// error; for view columns that are expressions we are given NULL for their origins
|
||||
// so we don't need special handling for that case either.
|
||||
//
|
||||
// this is confirmed in the `tests/sqlite-macros.rs` integration test
|
||||
return Err(SqliteError::from_connection(self.connection.0.as_ptr()).into());
|
||||
}
|
||||
|
||||
Ok(Some(not_null != 0))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn params(&mut self) -> usize {
|
||||
// https://www.hwaci.com/sw/sqlite/c3ref/bind_parameter_count.html
|
||||
let num = unsafe { sqlite3_bind_parameter_count(self.handle()) };
|
||||
|
|
|
@ -12,6 +12,7 @@ async fn macro_select() -> anyhow::Result<()> {
|
|||
|
||||
assert_eq!(1, account.id);
|
||||
assert_eq!("Herp Derpinson", account.name);
|
||||
assert_eq!(account.is_active, None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -30,6 +31,7 @@ async fn macro_select_bind() -> anyhow::Result<()> {
|
|||
|
||||
assert_eq!(1, account.id);
|
||||
assert_eq!("Herp Derpinson", account.name);
|
||||
assert_eq!(account.is_active, None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -38,7 +40,7 @@ async fn macro_select_bind() -> anyhow::Result<()> {
|
|||
struct RawAccount {
|
||||
id: i32,
|
||||
name: String,
|
||||
is_active: bool,
|
||||
is_active: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
|
@ -50,8 +52,26 @@ async fn test_query_as_raw() -> anyhow::Result<()> {
|
|||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(1, account.id);
|
||||
assert_eq!("Herp Derpinson", account.name);
|
||||
assert_eq!(account.id, 1);
|
||||
assert_eq!(account.name, "Herp Derpinson");
|
||||
assert_eq!(account.is_active, None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn macro_select_from_view() -> anyhow::Result<()> {
|
||||
let mut conn = new::<Sqlite>().await?;
|
||||
|
||||
let account = sqlx::query!("SELECT id, name, is_active from accounts_view")
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
// SQLite tells us the true origin of these columns even through the view
|
||||
assert_eq!(account.id, 1);
|
||||
assert_eq!(account.name, "Herp Derpinson");
|
||||
assert_eq!(account.is_active, None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue