fix(sqlite): fallback to storage class when typing expressions and infer INTEGER as i64

This commit is contained in:
Ryan Leckey 2020-06-27 19:17:46 -07:00
parent 513d666217
commit cfa833fa0d
8 changed files with 99 additions and 26 deletions

View File

@ -10,6 +10,7 @@ use crate::type_info::TypeInfo;
#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))]
pub(crate) enum DataType {
Null,
Int,
Float,
Text,
@ -38,29 +39,30 @@ impl Display for SqliteTypeInfo {
impl TypeInfo for SqliteTypeInfo {
fn name(&self) -> &str {
match self.0 {
DataType::Null => "NULL",
DataType::Text => "TEXT",
DataType::Float => "FLOAT",
DataType::Blob => "BLOB",
DataType::Int => "INTEGER",
DataType::Int | DataType::Int64 => "INTEGER",
DataType::Numeric => "NUMERIC",
// non-standard extensions
DataType::Bool => "BOOLEAN",
DataType::Int64 => "BIGINT",
}
}
}
impl DataType {
pub(crate) fn from_code(code: c_int) -> Option<Self> {
pub(crate) fn from_code(code: c_int) -> Self {
match code {
SQLITE_INTEGER => Some(DataType::Int),
SQLITE_FLOAT => Some(DataType::Float),
SQLITE_BLOB => Some(DataType::Blob),
SQLITE_NULL => None,
SQLITE_TEXT => Some(DataType::Text),
SQLITE_INTEGER => DataType::Int,
SQLITE_FLOAT => DataType::Float,
SQLITE_BLOB => DataType::Blob,
SQLITE_NULL => DataType::Null,
SQLITE_TEXT => DataType::Text,
_ => None,
// https://sqlite.org/c3ref/c_blob.html
_ => panic!("unknown data type code {}", code),
}
}
}
@ -74,14 +76,11 @@ impl FromStr for DataType {
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_ascii_lowercase();
Ok(match &*s {
"int4" => DataType::Int,
"int8" => DataType::Int64,
"boolean" | "bool" => DataType::Bool,
_ if s.contains("int") && s.contains("big") && s.find("int") > s.find("big") => {
DataType::Int64
}
_ if s.contains("int") => DataType::Int,
_ if s.contains("int") => DataType::Int64,
_ if s.contains("char") || s.contains("clob") || s.contains("text") => DataType::Text,
@ -98,10 +97,12 @@ impl FromStr for DataType {
#[test]
fn test_data_type_from_str() -> Result<(), BoxDynError> {
assert_eq!(DataType::Int, "INT".parse()?);
assert_eq!(DataType::Int, "INTEGER".parse()?);
assert_eq!(DataType::Int, "INTBIG".parse()?);
assert_eq!(DataType::Int, "MEDIUMINT".parse()?);
assert_eq!(DataType::Int, "INT4".parse()?);
assert_eq!(DataType::Int64, "INT".parse()?);
assert_eq!(DataType::Int64, "INTEGER".parse()?);
assert_eq!(DataType::Int64, "INTBIG".parse()?);
assert_eq!(DataType::Int64, "MEDIUMINT".parse()?);
assert_eq!(DataType::Int64, "BIGINT".parse()?);
assert_eq!(DataType::Int64, "UNSIGNED BIG INT".parse()?);

View File

@ -9,6 +9,10 @@ impl Type<Sqlite> for bool {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Bool)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Bool | DataType::Int | DataType::Int64)
}
}
impl<'q> Encode<'q, Sqlite> for bool {

View File

@ -1,3 +1,5 @@
use std::convert::TryInto;
use crate::decode::Decode;
use crate::encode::{Encode, IsNull};
use crate::error::BoxDynError;
@ -5,10 +7,62 @@ use crate::sqlite::type_info::DataType;
use crate::sqlite::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef};
use crate::types::Type;
impl Type<Sqlite> for i8 {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Int)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Int | DataType::Int64)
}
}
impl<'q> Encode<'q, Sqlite> for i8 {
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
args.push(SqliteArgumentValue::Int(*self as i32));
IsNull::No
}
}
impl<'r> Decode<'r, Sqlite> for i8 {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.int().try_into()?)
}
}
impl Type<Sqlite> for i16 {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Int)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Int | DataType::Int64)
}
}
impl<'q> Encode<'q, Sqlite> for i16 {
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
args.push(SqliteArgumentValue::Int(*self as i32));
IsNull::No
}
}
impl<'r> Decode<'r, Sqlite> for i16 {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(value.int().try_into()?)
}
}
impl Type<Sqlite> for i32 {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Int)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Int | DataType::Int64)
}
}
impl<'q> Encode<'q, Sqlite> for i32 {
@ -29,6 +83,10 @@ impl Type<Sqlite> for i64 {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo(DataType::Int64)
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
matches!(ty.0, DataType::Int | DataType::Int64)
}
}
impl<'q> Encode<'q, Sqlite> for i64 {

View File

@ -83,9 +83,15 @@ impl<'r> ValueRef<'r> for SqliteValueRef<'r> {
fn type_info(&self) -> Option<Cow<'_, SqliteTypeInfo>> {
match self.0 {
SqliteValueData::Statement { statement, index } => {
statement.column_decltype(index).map(Cow::Owned)
}
SqliteValueData::Statement { statement, index } => statement
.column_decltype(index)
.or_else(|| {
// fall back to the storage class for expressions
Some(SqliteTypeInfo(DataType::from_code(
statement.column_type(index),
)))
})
.map(Cow::Owned),
SqliteValueData::Value(v) => v.type_info(),
}
@ -115,7 +121,7 @@ impl SqliteValue {
Self(Arc::new(NonNull::new_unchecked(sqlite3_value_dup(value))))
}
fn r#type(&self) -> Option<DataType> {
fn r#type(&self) -> DataType {
DataType::from_code(unsafe { sqlite3_value_type(self.0.as_ptr()) })
}
@ -158,7 +164,7 @@ impl Value for SqliteValue {
}
fn type_info(&self) -> Option<Cow<'_, SqliteTypeInfo>> {
self.r#type().map(SqliteTypeInfo).map(Cow::Owned)
Some(Cow::Owned(SqliteTypeInfo(self.r#type())))
}
fn is_null(&self) -> bool {

View File

@ -72,6 +72,10 @@ fn expand_derive_has_sql_type_transparent(
fn type_info() -> DB::TypeInfo {
<#ty as sqlx::Type<DB>>::type_info()
}
fn compatible(ty: &DB::TypeInfo) -> bool {
<#ty as sqlx::Type<DB>>::compatible(ty)
}
}
));
}

View File

@ -29,10 +29,10 @@ async fn it_describes_simple() -> anyhow::Result<()> {
let column_type_names = type_names(&columns);
assert_eq!(column_type_names[0], "BIGINT");
assert_eq!(column_type_names[0], "INTEGER");
assert_eq!(column_type_names[1], "TEXT");
assert_eq!(column_type_names[2], "BOOLEAN");
assert_eq!(column_type_names[3], "BIGINT");
assert_eq!(column_type_names[3], "INTEGER");
Ok(())
}

View File

@ -36,7 +36,7 @@ async fn macro_select_bind() -> anyhow::Result<()> {
#[derive(Debug)]
struct RawAccount {
id: i32,
id: i64,
name: String,
is_active: Option<bool>,
}

Binary file not shown.