mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-12-29 21:00:54 +00:00
sqlite: implement describe
This commit is contained in:
parent
5f27026459
commit
0130fe1479
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1660,7 +1660,6 @@ dependencies = [
|
||||
"generic-array",
|
||||
"hex",
|
||||
"hmac",
|
||||
"libc",
|
||||
"libsqlite3-sys",
|
||||
"log",
|
||||
"matches",
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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(),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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};
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user