sqlite: implement describe

This commit is contained in:
Ryan Leckey 2020-03-14 14:57:26 -07:00
parent 5f27026459
commit 0130fe1479
14 changed files with 172 additions and 81 deletions

1
Cargo.lock generated
View File

@ -1660,7 +1660,6 @@ dependencies = [
"generic-array",
"hex",
"hmac",
"libc",
"libsqlite3-sys",
"log",
"matches",

View File

@ -17,7 +17,7 @@ default = [ "runtime-async-std" ]
unstable = []
postgres = [ "md-5", "sha2", "base64", "sha-1", "rand", "hmac" ]
mysql = [ "sha-1", "sha2", "generic-array", "num-bigint", "base64", "digest", "rand" ]
sqlite = [ "libc", "libsqlite3-sys" ]
sqlite = [ "libsqlite3-sys" ]
tls = [ "async-native-tls" ]
runtime-async-std = [ "async-native-tls/runtime-async-std", "async-std" ]
runtime-tokio = [ "async-native-tls/runtime-tokio", "tokio" ]
@ -50,7 +50,6 @@ sha2 = { version = "0.8.1", default-features = false, optional = true }
tokio = { version = "0.2.13", default-features = false, features = [ "dns", "fs", "time", "tcp" ], optional = true }
url = { version = "2.1.1", default-features = false }
uuid = { version = "0.8.1", default-features = false, optional = true, features = [ "std" ] }
libc = { version = "0.2", optional = true }
# <https://github.com/jgallagher/rusqlite/tree/master/libsqlite3-sys>
[dependencies.libsqlite3-sys]

View File

@ -1,6 +1,7 @@
use core::ffi::c_void;
use libc::c_int;
use std::os::raw::c_int;
use libsqlite3_sys::{
sqlite3_bind_blob, sqlite3_bind_double, sqlite3_bind_int, sqlite3_bind_int64,
sqlite3_bind_null, sqlite3_bind_text, SQLITE_OK,

View File

@ -3,7 +3,6 @@ use core::ptr::{null, null_mut, NonNull};
use std::collections::HashMap;
use std::convert::TryInto;
use std::ffi::CString;
use std::sync::Arc;
use futures_core::future::BoxFuture;
use futures_util::future;
@ -22,7 +21,6 @@ pub struct SqliteConnection {
pub(super) handle: NonNull<sqlite3>,
pub(super) statements: Vec<SqliteStatement>,
pub(super) statement_by_query: HashMap<String, usize>,
pub(super) columns_by_query: HashMap<String, Arc<HashMap<String, usize>>>,
}
// SAFE: A sqlite3 handle is safe to access from multiple threads provided
@ -70,7 +68,6 @@ fn establish(url: crate::Result<Url>) -> crate::Result<SqliteConnection> {
handle: NonNull::new(handle).unwrap(),
statements: Vec::with_capacity(10),
statement_by_query: HashMap::with_capacity(10),
columns_by_query: HashMap::new(),
})
}
@ -81,7 +78,9 @@ impl Connect for SqliteConnection {
Self: Sized,
{
let url = url.try_into();
Box::pin(spawn_blocking(move || establish(url)))
let conn = establish(url);
Box::pin(future::ready(conn))
}
}

View File

@ -1,6 +1,3 @@
use std::collections::HashMap;
use std::sync::Arc;
use futures_core::future::BoxFuture;
use crate::connection::ConnectionSource;
@ -22,7 +19,6 @@ enum State<'q> {
pub struct SqliteCursor<'c, 'q> {
source: ConnectionSource<'c, SqliteConnection>,
columns: Arc<HashMap<Box<str>, usize>>,
state: State<'q>,
}
@ -36,7 +32,6 @@ impl<'c, 'q> Cursor<'c, 'q> for SqliteCursor<'c, 'q> {
{
Self {
source: ConnectionSource::Pool(pool.clone()),
columns: Arc::default(),
state: State::Query(query.into_parts()),
}
}
@ -48,7 +43,6 @@ impl<'c, 'q> Cursor<'c, 'q> for SqliteCursor<'c, 'q> {
{
Self {
source: ConnectionSource::Connection(conn.into()),
columns: Arc::default(),
state: State::Query(query.into_parts()),
}
}

View File

@ -1,7 +1,7 @@
use crate::error::DatabaseError;
use libc::c_int;
use libsqlite3_sys::sqlite3_errstr;
use std::ffi::CStr;
use std::os::raw::c_int;
pub struct SqliteError {
#[allow(dead_code)]

View File

@ -3,14 +3,13 @@ use futures_core::future::BoxFuture;
use libsqlite3_sys::sqlite3_changes;
use crate::cursor::Cursor;
use crate::describe::Describe;
use crate::describe::{Column, Describe};
use crate::executor::{Execute, Executor, RefExecutor};
use crate::maybe_owned::MaybeOwned;
use crate::sqlite::arguments::SqliteArguments;
use crate::sqlite::cursor::SqliteCursor;
use crate::sqlite::statement::{SqliteStatement, Step};
use crate::sqlite::{Sqlite, SqliteConnection};
use std::collections::HashMap;
use crate::sqlite::types::SqliteType;
use crate::sqlite::{Sqlite, SqliteConnection, SqliteTypeInfo};
impl SqliteConnection {
pub(super) fn prepare(
@ -74,7 +73,7 @@ impl Executor for SqliteConnection {
Box::pin(async move {
let mut statement = self.prepare(query, arguments.is_some())?;
let mut statement_ = statement.resolve(&mut self.statements);
let statement_ = statement.resolve(&mut self.statements);
if let Some(arguments) = &mut arguments {
statement_.bind(arguments)?;
@ -102,8 +101,59 @@ impl Executor for SqliteConnection {
where
E: Execute<'q, Self::Database>,
{
// Box::pin(async move { self.describe(query.into_parts().0).await })
todo!()
Box::pin(async move {
let (query, _) = query.into_parts();
let mut statement = self.prepare(query, false)?;
let statement = statement.resolve(&mut self.statements);
// 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();
// Next, collect (return) column types and names
let num_columns = statement.num_columns();
let mut columns = Vec::with_capacity(num_columns);
for i in 0..num_columns {
let name = statement.column_name(i);
let decl = statement.column_decltype(i);
let r#type = match decl {
None => SqliteType::Null,
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,
_ if decl.contains("int") => SqliteType::Integer,
_ if decl.contains("char") => SqliteType::Text,
_ => SqliteType::Null,
},
};
columns.push(Column {
name: Some(name.into()),
non_null: None,
table_id: None,
type_info: SqliteTypeInfo {
r#type,
affinity: None,
},
})
}
Ok(Describe {
param_types: params,
result_columns: columns.into_boxed_slice(),
})
})
}
}

View File

@ -1,8 +1,5 @@
use std::collections::HashMap;
use std::sync::Arc;
use libc::c_int;
use libsqlite3_sys::sqlite3_data_count;
use std::os::raw::c_int;
use crate::database::HasRow;
use crate::row::{ColumnIndex, Row};

View File

@ -4,11 +4,13 @@ use core::ptr::{null_mut, NonNull};
use std::collections::HashMap;
use std::ffi::CStr;
use std::os::raw::c_int;
use libsqlite3_sys::{
sqlite3, sqlite3_column_count, sqlite3_column_name, 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, sqlite3_bind_parameter_count, sqlite3_column_count, sqlite3_column_decltype,
sqlite3_column_name, sqlite3_finalize, sqlite3_prepare_v3, sqlite3_reset, sqlite3_step,
sqlite3_stmt, SQLITE_DONE, SQLITE_OK, SQLITE_PREPARE_NO_VTAB, SQLITE_PREPARE_PERSISTENT,
SQLITE_ROW,
};
use crate::sqlite::SqliteArguments;
@ -75,24 +77,46 @@ impl SqliteStatement {
})
}
pub(super) fn num_columns(&self) -> usize {
// https://sqlite.org/c3ref/column_count.html
#[allow(unsafe_code)]
let count = unsafe { sqlite3_column_count(self.handle.as_ptr()) };
count as usize
}
pub(super) fn column_name(&self, index: usize) -> &str {
// https://sqlite.org/c3ref/column_name.html
#[allow(unsafe_code)]
let name =
unsafe { CStr::from_ptr(sqlite3_column_name(self.handle.as_ptr(), index as c_int)) };
name.to_str().unwrap()
}
pub(super) fn column_decltype(&self, index: usize) -> Option<&str> {
// https://sqlite.org/c3ref/column_name.html
#[allow(unsafe_code)]
let name = unsafe {
let ptr = sqlite3_column_decltype(self.handle.as_ptr(), index as c_int);
if ptr.is_null() {
None
} else {
Some(CStr::from_ptr(ptr))
}
};
name.map(|s| s.to_str().unwrap())
}
pub(super) fn columns<'a>(&'a self) -> impl Deref<Target = HashMap<String, usize>> + 'a {
RefMut::map(self.columns.borrow_mut(), |columns| {
columns.get_or_insert_with(|| {
// https://sqlite.org/c3ref/column_count.html
#[allow(unsafe_code)]
let count = unsafe { sqlite3_column_count(self.handle.as_ptr()) };
let count = count as usize;
let count = self.num_columns();
let mut columns = HashMap::with_capacity(count);
for i in 0..count {
// https://sqlite.org/c3ref/column_name.html
#[allow(unsafe_code)]
let name =
unsafe { CStr::from_ptr(sqlite3_column_name(self.handle.as_ptr(), 0)) };
let name = name.to_str().unwrap().to_owned();
columns.insert(name, i);
columns.insert(self.column_name(i).to_owned(), i);
}
columns
@ -100,6 +124,13 @@ impl SqliteStatement {
})
}
pub(super) fn params(&self) -> usize {
// https://www.hwaci.com/sw/sqlite/c3ref/bind_parameter_count.html
#[allow(unsafe_code)]
let num = unsafe { sqlite3_bind_parameter_count(self.handle.as_ptr()) };
num as usize
}
pub(super) fn bind(&mut self, arguments: &mut SqliteArguments) -> crate::Result<()> {
for (index, value) in arguments.values.iter().enumerate() {
value.bind(self, index + 1)?;
@ -125,8 +156,8 @@ impl SqliteStatement {
let status = unsafe { sqlite3_step(self.handle.as_ptr()) };
match status {
SQLITE_ROW => Ok(Step::Row),
SQLITE_DONE => Ok(Step::Done),
SQLITE_ROW => Ok(Step::Row),
status => {
return Err(SqliteError::new(status).into());

View File

@ -6,7 +6,7 @@ use crate::types::Type;
impl Type<Sqlite> for bool {
fn type_info() -> SqliteTypeInfo {
SqliteTypeInfo::new(SqliteType::Integer, SqliteTypeAffinity::Numeric)
SqliteTypeInfo::new(SqliteType::Boolean, SqliteTypeAffinity::Numeric)
}
}

View File

@ -16,14 +16,17 @@ mod str;
pub(crate) enum SqliteType {
Integer = 1,
Float = 2,
Text = 3,
Blob = 4,
Null = 5,
Text = 3,
// Non-standard extensions
Boolean,
}
// https://www.sqlite.org/datatype3.html#type_affinity
#[derive(Debug, PartialEq, Clone, Copy)]
enum SqliteTypeAffinity {
pub(crate) enum SqliteTypeAffinity {
Text,
Numeric,
Integer,
@ -33,24 +36,28 @@ enum SqliteTypeAffinity {
#[derive(Debug, Clone)]
pub struct SqliteTypeInfo {
r#type: SqliteType,
affinity: SqliteTypeAffinity,
pub(crate) r#type: SqliteType,
pub(crate) affinity: Option<SqliteTypeAffinity>,
}
impl SqliteTypeInfo {
fn new(r#type: SqliteType, affinity: SqliteTypeAffinity) -> Self {
Self { r#type, affinity }
Self {
r#type,
affinity: Some(affinity),
}
}
}
impl Display for SqliteTypeInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self.affinity {
SqliteTypeAffinity::Text => "TEXT",
SqliteTypeAffinity::Numeric => "NUMERIC",
SqliteTypeAffinity::Integer => "INTEGER",
SqliteTypeAffinity::Real => "REAL",
SqliteTypeAffinity::Blob => "BLOB",
f.write_str(match self.r#type {
SqliteType::Null => "NULL",
SqliteType::Text => "TEXT",
SqliteType::Boolean => "BOOLEAN",
SqliteType::Integer => "INTEGER",
SqliteType::Float => "DOUBLE",
SqliteType::Blob => "BLOB",
})
}
}

View File

@ -18,18 +18,6 @@ async fn test_empty_query() -> anyhow::Result<()> {
Ok(())
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn test_multi_cursor() -> anyhow::Result<()> {
let mut conn = new::<Postgres>().await?;
let query = format!("SELECT {} as _1", 10);
let _cursor1 = conn.fetch(&*query);
let _cursor2 = conn.fetch(&*query);
Ok(())
}
/// Test a simple select expression. This should return the row.
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]

View File

@ -2,18 +2,3 @@
use sqlx::{Cursor, Executor, Row, Sqlite};
use sqlx_test::new;
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn test_multi_cursor() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let query = format!("SELECT {} as _1", 10);
let mut cursor1 = conn.fetch(&*query);
let mut cursor2 = conn.fetch(&*query);
// let row = cursor.next().await?.unwrap();
// assert!(5i32 == row.try_get::<i32, _>(0)?);
Ok(())
}

View File

@ -53,3 +53,44 @@ CREATE TEMPORARY TABLE users (id INTEGER PRIMARY KEY)
Ok(())
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn it_describes() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let _ = conn
.execute(
r#"
CREATE TEMPORARY TABLE describe_test (
_1 int primary key,
_2 text not null,
_3 blob,
_4 boolean,
_5 float,
_6 varchar(255),
_7 double,
_8 bigint
)
"#,
)
.await?;
let describe = conn
.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");
// Expressions can not be described
assert_eq!(describe.result_columns[8].type_info.to_string(), "NULL");
Ok(())
}