remove is_null_type and use Option<TypeInfo>

This commit is contained in:
Ryan Leckey 2020-03-17 21:43:41 -07:00
parent 927e2b1586
commit d380f4b4a8
12 changed files with 175 additions and 99 deletions

View File

@ -11,7 +11,7 @@ where
DB: Database + ?Sized,
{
/// The expected types for the parameters of the query.
pub param_types: Box<[DB::TypeInfo]>,
pub param_types: Box<[Option<DB::TypeInfo>]>,
/// The type and table information, if any for the results of the query.
pub result_columns: Box<[Column<DB>]>,
@ -39,7 +39,7 @@ where
{
pub name: Option<Box<str>>,
pub table_id: Option<DB::TableId>,
pub type_info: DB::TypeInfo,
pub type_info: Option<DB::TypeInfo>,
/// Whether or not the column cannot be `NULL` (or if that is even knowable).
pub non_null: Option<bool>,
}

View File

@ -43,13 +43,17 @@ impl MySqlTypeInfo {
}
}
pub(crate) fn from_column_def(def: &ColumnDefinition) -> Self {
Self {
pub(crate) fn from_column_def(def: &ColumnDefinition) -> Option<Self> {
if def.type_id == TypeId::NULL {
return None;
}
Some(Self {
id: def.type_id,
is_unsigned: def.flags.contains(FieldFlags::UNSIGNED),
is_binary: def.flags.contains(FieldFlags::BINARY),
char_set: def.char_set,
}
})
}
#[doc(hidden)]
@ -106,10 +110,6 @@ impl TypeInfo for MySqlTypeInfo {
_ => self.id.0 == other.id.0 && self.is_unsigned == other.is_unsigned,
}
}
fn is_null_type(&self) -> bool {
self.id == TypeId::NULL
}
}
impl<'de, T> Decode<'de, MySql> for Option<T>

View File

@ -199,7 +199,7 @@ impl PgConnection {
param_types: params
.ids
.iter()
.map(|id| PgTypeInfo::new(*id, &type_names[&id.0]))
.map(|id| Some(PgTypeInfo::new(*id, &type_names[&id.0])))
.collect::<Vec<_>>()
.into_boxed_slice(),
result_columns: self
@ -315,7 +315,10 @@ impl PgConnection {
Ok(Column {
name: field.name,
table_id: field.table_id,
type_info: PgTypeInfo::new(field.type_id, &type_names[&field.type_id.0]),
type_info: Some(PgTypeInfo::new(
field.type_id,
&type_names[&field.type_id.0],
)),
non_null,
})
})

View File

@ -81,11 +81,6 @@ impl TypeInfo for PgTypeInfo {
// TODO: 99% of postgres types are direct equality for [compatible]; when we add something that isn't (e.g, JSON/JSONB), fix this here
self.id.0 == other.id.0
}
fn is_null_type(&self) -> bool {
// Postgres doesn't have a "null" type
false
}
}
impl<'de, T> Decode<'de, Postgres> for Option<T>

View File

@ -138,14 +138,7 @@ impl Executor for SqliteConnection {
// First let's attempt to describe what we can about parameter types
// Which happens to just be the count, heh
let num_params = statement.params();
let params = vec![
SqliteTypeInfo {
r#type: SqliteType::Null,
affinity: None,
};
num_params
]
.into_boxed_slice();
let params = vec![None; num_params].into_boxed_slice();
// Next, collect (return) column types and names
let num_columns = statement.column_count();
@ -155,15 +148,15 @@ impl Executor for SqliteConnection {
let decl = statement.column_decltype(i);
let r#type = match decl {
None => SqliteType::Null,
None => None,
Some(decl) => match &*decl.to_ascii_lowercase() {
"bool" | "boolean" => SqliteType::Boolean,
"clob" | "text" => SqliteType::Text,
"blob" => SqliteType::Blob,
"real" | "double" | "double precision" | "float" => SqliteType::Float,
decl @ _ if decl.contains("int") => SqliteType::Integer,
decl @ _ if decl.contains("char") => SqliteType::Text,
_ => SqliteType::Null,
"bool" | "boolean" => Some(SqliteType::Boolean),
"clob" | "text" => Some(SqliteType::Text),
"blob" => Some(SqliteType::Blob),
"real" | "double" | "double precision" | "float" => Some(SqliteType::Float),
decl @ _ if decl.contains("int") => Some(SqliteType::Integer),
decl @ _ if decl.contains("char") => Some(SqliteType::Text),
_ => None,
},
};
@ -171,10 +164,10 @@ impl Executor for SqliteConnection {
name: Some(name.into()),
non_null: None,
table_id: None,
type_info: SqliteTypeInfo {
type_info: r#type.map(|r#type| SqliteTypeInfo {
r#type,
affinity: None,
},
}),
})
}

View File

@ -66,10 +66,6 @@ impl TypeInfo for SqliteTypeInfo {
fn compatible(&self, other: &Self) -> bool {
self.r#type == other.r#type || self.affinity == other.affinity
}
fn is_null_type(&self) -> bool {
self.r#type == SqliteType::Null
}
}
impl<'de, T> Decode<'de, Sqlite> for Option<T>

View File

@ -18,14 +18,6 @@ pub trait TypeInfo: Debug + Display + Clone {
/// Compares type information to determine if `other` is compatible at the Rust level
/// with `self`.
fn compatible(&self, other: &Self) -> bool;
/// Return `true` if this is the database flavor's "null" sentinel type.
///
/// For type info coming from the description of a prepared statement, this means
/// that the server could not infer the expected type of a bind parameter or result column;
/// the latter is most often the case with columns that are the result of an expression
/// in a weakly-typed database like MySQL or SQLite.
fn is_null_type(&self) -> bool;
}
/// Indicates that a SQL type is supported for a database.

View File

@ -30,11 +30,14 @@ pub fn quote_args<DB: DatabaseExt>(
.iter()
.zip(input.arg_names.iter().zip(&input.arg_exprs))
.enumerate()
.map(|(i, (param_ty, (name, expr)))| -> crate::Result<_>{
.map(|(i, (param_ty, (name, expr)))| -> crate::Result<_> {
// TODO: We could remove the ParamChecking flag and just filter to only test params that are non-null
let param_ty = param_ty.as_ref().unwrap();
let param_ty = get_type_override(expr)
.or_else(|| {
Some(
DB::param_type_for_id(param_ty)?
DB::param_type_for_id(&param_ty)?
.parse::<proc_macro2::TokenStream>()
.unwrap(),
)

View File

@ -45,44 +45,51 @@ pub fn columns_to_rust<DB: DatabaseExt>(describe: &Describe<DB>) -> crate::Resul
let ident = parse_ident(name)?;
let type_ = <DB as DatabaseExt>::return_type_for_id(&column.type_info)
.ok_or_else(|| {
if let Some(feature_gate) =
<DB as DatabaseExt>::get_feature_gate(&column.type_info)
{
format!(
"optional feature `{feat}` required for type {ty} of {col}",
ty = &column.type_info,
feat = feature_gate,
col = DisplayColumn {
idx: i,
name: column.name.as_deref()
}
)
} else if column.type_info.is_null_type() {
format!(
"database couldn't tell us the type of {col}; \
this can happen for columns that are the result of an expression",
col = DisplayColumn {
idx: i,
name: column.name.as_deref()
}
)
} else {
format!(
"unsupported type {ty} of {col}",
ty = column.type_info,
col = DisplayColumn {
idx: i,
name: column.name.as_deref()
}
)
let type_ = if let Some(type_info) = &column.type_info {
<DB as DatabaseExt>::return_type_for_id(&type_info)
.ok_or_else(|| {
if let Some(feature_gate) =
<DB as DatabaseExt>::get_feature_gate(&type_info)
{
format!(
"optional feature `{feat}` required for type {ty} of {col}",
ty = &type_info,
feat = feature_gate,
col = DisplayColumn {
idx: i,
name: column.name.as_deref()
}
)
} else {
format!(
"unsupported type {ty} of {col}",
ty = type_info,
col = DisplayColumn {
idx: i,
name: column.name.as_deref()
}
)
}
})?
.parse::<TokenStream>()
.unwrap()
} else {
format!(
"database couldn't tell us the type of {col}; \
this can happen for columns that are the result of an expression",
col = DisplayColumn {
idx: i,
name: column.name.as_deref()
}
})?
)
.parse::<TokenStream>()
.unwrap();
.unwrap()
};
Ok(RustColumn { ident, type_ })
Ok(RustColumn {
ident,
type_: type_,
})
})
.collect::<crate::Result<Vec<_>>>()
}

View File

@ -230,13 +230,41 @@ async fn test_describe() -> anyhow::Result<()> {
.await?;
assert_eq!(describe.result_columns[0].non_null, Some(true));
assert_eq!(describe.result_columns[0].type_info.type_name(), "INT4");
assert_eq!(
describe.result_columns[0]
.type_info
.as_ref()
.unwrap()
.type_name(),
"INT4"
);
assert_eq!(describe.result_columns[1].non_null, Some(true));
assert_eq!(describe.result_columns[1].type_info.type_name(), "TEXT");
assert_eq!(
describe.result_columns[1]
.type_info
.as_ref()
.unwrap()
.type_name(),
"TEXT"
);
assert_eq!(describe.result_columns[2].non_null, Some(false));
assert_eq!(describe.result_columns[2].type_info.type_name(), "BYTEA");
assert_eq!(
describe.result_columns[2]
.type_info
.as_ref()
.unwrap()
.type_name(),
"BYTEA"
);
assert_eq!(describe.result_columns[3].non_null, None);
assert_eq!(describe.result_columns[3].type_info.type_name(), "BOOL");
assert_eq!(
describe.result_columns[3]
.type_info
.as_ref()
.unwrap()
.type_name(),
"BOOL"
);
Ok(())
}

View File

@ -21,9 +21,12 @@ async fn macro_select() -> anyhow::Result<()> {
async fn macro_select_bind() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let account = sqlx::query!("select id, name, is_active from accounts where id = ?", 1i32)
.fetch_one(&mut conn)
.await?;
let account = sqlx::query!(
"select id, name, is_active from accounts where id = ?",
1i32
)
.fetch_one(&mut conn)
.await?;
assert_eq!(1, account.id);
assert_eq!("Herp Derpinson", account.name);

View File

@ -129,17 +129,73 @@ CREATE TEMPORARY TABLE describe_test (
.describe("select nt.*, false from describe_test nt")
.await?;
assert_eq!(describe.result_columns[0].type_info.to_string(), "INTEGER");
assert_eq!(describe.result_columns[1].type_info.to_string(), "TEXT");
assert_eq!(describe.result_columns[2].type_info.to_string(), "BLOB");
assert_eq!(describe.result_columns[3].type_info.to_string(), "BOOLEAN");
assert_eq!(describe.result_columns[4].type_info.to_string(), "DOUBLE");
assert_eq!(describe.result_columns[5].type_info.to_string(), "TEXT");
assert_eq!(describe.result_columns[6].type_info.to_string(), "DOUBLE");
assert_eq!(describe.result_columns[7].type_info.to_string(), "INTEGER");
assert_eq!(
describe.result_columns[0]
.type_info
.as_ref()
.unwrap()
.to_string(),
"INTEGER"
);
assert_eq!(
describe.result_columns[1]
.type_info
.as_ref()
.unwrap()
.to_string(),
"TEXT"
);
assert_eq!(
describe.result_columns[2]
.type_info
.as_ref()
.unwrap()
.to_string(),
"BLOB"
);
assert_eq!(
describe.result_columns[3]
.type_info
.as_ref()
.unwrap()
.to_string(),
"BOOLEAN"
);
assert_eq!(
describe.result_columns[4]
.type_info
.as_ref()
.unwrap()
.to_string(),
"DOUBLE"
);
assert_eq!(
describe.result_columns[5]
.type_info
.as_ref()
.unwrap()
.to_string(),
"TEXT"
);
assert_eq!(
describe.result_columns[6]
.type_info
.as_ref()
.unwrap()
.to_string(),
"DOUBLE"
);
assert_eq!(
describe.result_columns[7]
.type_info
.as_ref()
.unwrap()
.to_string(),
"INTEGER"
);
// Expressions can not be described
assert_eq!(describe.result_columns[8].type_info.to_string(), "NULL");
assert!(describe.result_columns[8].type_info.is_none());
Ok(())
}