From cfa833fa0d18c56331f5c738f5213a953c9ce844 Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Sat, 27 Jun 2020 19:17:46 -0700 Subject: [PATCH] fix(sqlite): fallback to storage class when typing expressions and infer INTEGER as i64 --- sqlx-core/src/sqlite/type_info.rs | 37 +++++++++--------- sqlx-core/src/sqlite/types/bool.rs | 4 ++ sqlx-core/src/sqlite/types/int.rs | 58 +++++++++++++++++++++++++++++ sqlx-core/src/sqlite/value.rs | 16 +++++--- sqlx-macros/src/derives/type.rs | 4 ++ tests/sqlite/describe.rs | 4 +- tests/sqlite/macros.rs | 2 +- tests/sqlite/sqlite.db | Bin 36864 -> 36864 bytes 8 files changed, 99 insertions(+), 26 deletions(-) diff --git a/sqlx-core/src/sqlite/type_info.rs b/sqlx-core/src/sqlite/type_info.rs index 21bad685..6818f821 100644 --- a/sqlx-core/src/sqlite/type_info.rs +++ b/sqlx-core/src/sqlite/type_info.rs @@ -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 { + 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 { 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()?); diff --git a/sqlx-core/src/sqlite/types/bool.rs b/sqlx-core/src/sqlite/types/bool.rs index c92389b7..46a69554 100644 --- a/sqlx-core/src/sqlite/types/bool.rs +++ b/sqlx-core/src/sqlite/types/bool.rs @@ -9,6 +9,10 @@ impl Type 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 { diff --git a/sqlx-core/src/sqlite/types/int.rs b/sqlx-core/src/sqlite/types/int.rs index a25e7762..1fd13c46 100644 --- a/sqlx-core/src/sqlite/types/int.rs +++ b/sqlx-core/src/sqlite/types/int.rs @@ -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 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>) -> IsNull { + args.push(SqliteArgumentValue::Int(*self as i32)); + + IsNull::No + } +} + +impl<'r> Decode<'r, Sqlite> for i8 { + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(value.int().try_into()?) + } +} + +impl Type 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>) -> IsNull { + args.push(SqliteArgumentValue::Int(*self as i32)); + + IsNull::No + } +} + +impl<'r> Decode<'r, Sqlite> for i16 { + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(value.int().try_into()?) + } +} + impl Type 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 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 { diff --git a/sqlx-core/src/sqlite/value.rs b/sqlx-core/src/sqlite/value.rs index 6d19f19e..b89d05a9 100644 --- a/sqlx-core/src/sqlite/value.rs +++ b/sqlx-core/src/sqlite/value.rs @@ -83,9 +83,15 @@ impl<'r> ValueRef<'r> for SqliteValueRef<'r> { fn type_info(&self) -> Option> { 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 { + 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> { - self.r#type().map(SqliteTypeInfo).map(Cow::Owned) + Some(Cow::Owned(SqliteTypeInfo(self.r#type()))) } fn is_null(&self) -> bool { diff --git a/sqlx-macros/src/derives/type.rs b/sqlx-macros/src/derives/type.rs index ad41dba5..127882fb 100644 --- a/sqlx-macros/src/derives/type.rs +++ b/sqlx-macros/src/derives/type.rs @@ -72,6 +72,10 @@ fn expand_derive_has_sql_type_transparent( fn type_info() -> DB::TypeInfo { <#ty as sqlx::Type>::type_info() } + + fn compatible(ty: &DB::TypeInfo) -> bool { + <#ty as sqlx::Type>::compatible(ty) + } } )); } diff --git a/tests/sqlite/describe.rs b/tests/sqlite/describe.rs index ffd00d5a..eac56fee 100644 --- a/tests/sqlite/describe.rs +++ b/tests/sqlite/describe.rs @@ -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(()) } diff --git a/tests/sqlite/macros.rs b/tests/sqlite/macros.rs index 6c520f0e..88c5d927 100644 --- a/tests/sqlite/macros.rs +++ b/tests/sqlite/macros.rs @@ -36,7 +36,7 @@ async fn macro_select_bind() -> anyhow::Result<()> { #[derive(Debug)] struct RawAccount { - id: i32, + id: i64, name: String, is_active: Option, } diff --git a/tests/sqlite/sqlite.db b/tests/sqlite/sqlite.db index 9b925366fb4188048686712968aec3c400bdccc6..fa00c6fb1e0d674cdf9624d6e752d2b3e3134353 100644 GIT binary patch delta 287 zcmZozz|^pSX@WGP&O{k!RviXC`_#X3J*eocpg0EgsfSEzFBqOs}0f-V6N>YnU1o<(A_%MZdF@<O>i5R#gT)jm(WH3-rY}`9m4_-}2w$KhD2}e<6P#e>Hywf9PgG r0c-xrq4w*6@>dx6|L}k3{{&R^g8wo9J^q{gSAg=n_%~ni*Kq&