sqlite: initial work in connection

This commit is contained in:
Ryan Leckey 2020-03-11 11:01:17 -07:00
parent 5d042e35b1
commit 7ab07016da
16 changed files with 245 additions and 33 deletions

13
Cargo.lock generated
View File

@ -864,6 +864,17 @@ version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
[[package]]
name = "libsqlite3-sys"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "266eb8c361198e8d1f682bc974e5d9e2ae90049fb1943890904d11dad7d4a77d"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "lock_api"
version = "0.3.3"
@ -1649,6 +1660,8 @@ dependencies = [
"generic-array",
"hex",
"hmac",
"libc",
"libsqlite3-sys",
"log",
"matches",
"md-5",

View File

@ -71,6 +71,10 @@ required-features = [ "postgres", "macros" ]
name = "mysql-macros"
required-features = [ "mysql", "macros" ]
[[test]]
name = "sqlite"
required-features = [ "sqlite" ]
[[test]]
name = "mysql"
required-features = [ "mysql" ]

View File

@ -17,14 +17,14 @@ 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 = [ ]
sqlite = [ "libc", "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" ]
[dependencies]
async-native-tls = { version = "0.3.2", default-features = false, optional = true }
async-std = { version = "1.5.0", optional = true }
async-std = { version = "1.5.0", features = [ "unstable" ], optional = true }
async-stream = { version = "0.2.1", default-features = false }
base64 = { version = "0.11.0", default-features = false, optional = true, features = [ "std" ] }
bitflags = { version = "1.2.1", default-features = false }
@ -50,6 +50,14 @@ 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]
version = "0.17.1"
optional = true
default-features = false
features = [ "pkg-config", "vcpkg", "bundled" ]
[dev-dependencies]
matches = "0.1.8"

View File

@ -6,7 +6,7 @@ pub use async_std::{
io::{Read as AsyncRead, Write as AsyncWrite},
net::TcpStream,
task::sleep,
task::spawn,
task::{spawn, spawn_blocking},
task::yield_now,
};
@ -15,7 +15,7 @@ pub use tokio::{
fs,
io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
net::TcpStream,
task::spawn,
task::{spawn, spawn_blocking},
task::yield_now,
time::delay_for as sleep,
time::timeout,

View File

@ -2,23 +2,11 @@ use crate::arguments::Arguments;
use crate::encode::Encode;
use crate::sqlite::Sqlite;
use crate::types::Type;
#[derive(Debug, Clone)]
pub enum SqliteValue {
// TODO: Take by reference to remove the allocation
Text(String),
// TODO: Take by reference to remove the allocation
Blob(Vec<u8>),
Double(f64),
Int(i64),
}
use crate::sqlite::value::SqliteArgumentValue;
#[derive(Default)]
pub struct SqliteArguments {
values: Vec<SqliteValue>,
values: Vec<SqliteArgumentValue>,
}
impl Arguments for SqliteArguments {

View File

@ -1,11 +1,70 @@
use core::ptr::{NonNull, null, null_mut};
use std::convert::TryInto;
use std::ffi::CString;
use std::fmt::{self, Debug};
use futures_core::future::BoxFuture;
use libsqlite3_sys::{
sqlite3, sqlite3_open_v2, SQLITE_OK, SQLITE_OPEN_CREATE, SQLITE_OPEN_NOMUTEX,
SQLITE_OPEN_READWRITE, SQLITE_OPEN_SHAREDCACHE,
};
use crate::runtime::spawn_blocking;
use crate::connection::{Connect, Connection};
use crate::url::Url;
use futures_util::future;
use crate::sqlite::SqliteError;
pub struct SqliteConnection {}
#[derive(Debug)]
pub struct SqliteConnection {
pub(super) handle: NonNull<sqlite3>,
}
// SAFE: A sqlite3 handle is safe to access from multiple threads provided
// that only one thread access it at a time. Or in other words,
// the same guarantees that [Sync] requires. This is upheld as long
// [SQLITE_CONFIG_MULTITHREAD] is enabled and [SQLITE_THREADSAFE] was
// enabled when sqlite was compiled. We refuse to work if these conditions are
// not upheld, see [SqliteConnection::establish].
//
// <https://www.sqlite.org/c3ref/threadsafe.html>
// <https://www.sqlite.org/c3ref/c_config_covering_index_scan.html#sqliteconfigmultithread>
#[allow(unsafe_code)]
unsafe impl Send for SqliteConnection {}
#[allow(unsafe_code)]
unsafe impl Sync for SqliteConnection {}
fn establish(url: crate::Result<Url>) -> crate::Result<SqliteConnection> {
let url = url?;
let url = url.as_str().trim_start_matches("sqlite://");
// By default, we connect to an in-memory database.
// TODO: Handle the error when there are internal NULs in the database URL
let filename = CString::new(url).unwrap();
let mut handle = null_mut();
// [SQLITE_OPEN_NOMUTEX] will instruct [sqlite3_open_v2] to return an error if it
// cannot satisfy our wish for a thread-safe, lock-free connection object
let flags = SQLITE_OPEN_READWRITE
| SQLITE_OPEN_CREATE
| SQLITE_OPEN_NOMUTEX
| SQLITE_OPEN_SHAREDCACHE;
// <https://www.sqlite.org/c3ref/open.html>
#[allow(unsafe_code)]
let status = unsafe {
sqlite3_open_v2(filename.as_ptr(), &mut handle, flags, null())
};
if status != SQLITE_OK {
return Err(SqliteError::new(status).into());
}
Ok(SqliteConnection {
handle: NonNull::new(handle).unwrap(),
})
}
impl Connect for SqliteConnection {
fn connect<T>(url: T) -> BoxFuture<'static, crate::Result<SqliteConnection>>
@ -13,8 +72,8 @@ impl Connect for SqliteConnection {
T: TryInto<Url, Error = crate::Error>,
Self: Sized,
{
// Box::pin(SqliteConnection::new(url.try_into()))
todo!()
let url = url.try_into();
Box::pin(spawn_blocking(move || establish(url)))
}
}
@ -25,7 +84,7 @@ impl Connection for SqliteConnection {
}
fn ping(&mut self) -> BoxFuture<crate::Result<()>> {
//Box::pin(Executor::execute(self, "SELECT 1").map_ok(|_| ()))
todo!()
// For SQLite connections, PING does effectively nothing
Box::pin(future::ok(()))
}
}

View File

@ -1,5 +1,5 @@
use crate::database::{Database, HasCursor, HasRawValue, HasRow};
use crate::sqlite::arguments::SqliteValue;
use crate::sqlite::value::{SqliteResultValue, SqliteArgumentValue};
/// **Sqlite** database driver.
pub struct Sqlite;
@ -14,13 +14,13 @@ impl Database for Sqlite {
// TODO?
type TableId = u32;
type RawBuffer = Vec<SqliteValue>;
type RawBuffer = Vec<SqliteArgumentValue>;
}
impl<'a> HasRow<'a> for Sqlite {
impl<'c> HasRow<'c> for Sqlite {
type Database = Sqlite;
type Row = super::SqliteRow<'a>;
type Row = super::SqliteRow<'c>;
}
impl<'s, 'q> HasCursor<'s, 'q> for Sqlite {
@ -29,7 +29,6 @@ impl<'s, 'q> HasCursor<'s, 'q> for Sqlite {
type Cursor = super::SqliteCursor<'s, 'q>;
}
impl<'a> HasRawValue<'a> for Sqlite {
// TODO
type RawValue = Option<()>;
impl<'c> HasRawValue<'c> for Sqlite {
type RawValue = SqliteResultValue<'c>;
}

View File

@ -1,10 +1,28 @@
use crate::error::DatabaseError;
use libc::c_int;
use std::ffi::CStr;
use libsqlite3_sys::{sqlite3, sqlite3_errstr};
pub struct SqliteError;
pub struct SqliteError {
#[allow(dead_code)]
code: c_int,
message: String,
}
impl SqliteError {
pub(crate) fn new(code: c_int) -> Self {
#[allow(unsafe_code)]
let message = unsafe {
CStr::from_ptr(sqlite3_errstr(code))
};
Self { code, message: message.to_string_lossy().into_owned() }
}
}
impl DatabaseError for SqliteError {
fn message(&self) -> &str {
todo!()
&self.message
}
}

View File

@ -3,6 +3,7 @@ mod connection;
mod cursor;
mod database;
mod error;
mod value;
mod executor;
mod row;
mod types;
@ -14,3 +15,11 @@ pub use database::Sqlite;
pub use error::SqliteError;
pub use row::SqliteRow;
pub use types::SqliteTypeInfo;
/// An alias for [`Pool`][crate::Pool], specialized for **Sqlite**.
pub type SqlitePool = crate::pool::Pool<SqliteConnection>;
make_query_as!(SqliteQueryAs, Sqlite, SqliteRow);
impl_map_row_for_row!(Sqlite, SqliteRow);
impl_column_index_for_row!(Sqlite);
impl_from_row_for_tuples!(Sqlite, SqliteRow);

View File

@ -7,9 +7,11 @@ use std::sync::Arc;
use crate::error::UnexpectedNullError;
use crate::row::{ColumnIndex, Row};
use crate::sqlite::Sqlite;
use crate::sqlite::value::SqliteResultValue;
pub struct SqliteRow<'c> {
c: std::marker::PhantomData<&'c ()>,
pub(super) columns: Arc<HashMap<Box<str>, u16>>,
}
impl<'c> Row<'c> for SqliteRow<'c> {
@ -19,7 +21,7 @@ impl<'c> Row<'c> for SqliteRow<'c> {
todo!()
}
fn try_get_raw<'r, I>(&'r self, index: I) -> crate::Result<Option<()>>
fn try_get_raw<'r, I>(&'r self, index: I) -> crate::Result<SqliteResultValue<'c>>
where
I: ColumnIndex<Self::Database>,
{

View File

@ -0,0 +1,25 @@
use crate::types::Type;
use crate::sqlite::{Sqlite, SqliteTypeInfo};
use crate::encode::Encode;
use crate::sqlite::value::{SqliteArgumentValue, SqliteResultValue};
use crate::decode::Decode;
impl Type<Sqlite> for i32 {
fn type_info() -> SqliteTypeInfo {
// SqliteTypeInfo::new(ValueKind::Int)
todo!()
}
}
impl Encode<Sqlite> for i32 {
fn encode(&self, values: &mut Vec<SqliteArgumentValue>) {
values.push(SqliteArgumentValue::Int((*self).into()));
}
}
impl<'a> Decode<'a, Sqlite> for i32 {
fn decode(value: SqliteResultValue<'a>) -> crate::Result<i32> {
// Ok(value.int())
todo!()
}
}

View File

@ -2,6 +2,12 @@ use std::fmt::{self, Display};
use crate::types::TypeInfo;
// mod bool;
// mod bytes;
// mod float;
mod int;
// mod str;
#[derive(Debug, Clone)]
pub struct SqliteTypeInfo {}

View File

@ -0,0 +1,17 @@
#[derive(Debug, Clone)]
pub enum SqliteArgumentValue {
// TODO: Take by reference to remove the allocation
Text(String),
// TODO: Take by reference to remove the allocation
Blob(Vec<u8>),
Double(f64),
Int(i64),
}
pub struct SqliteResultValue<'c> {
// statement: SqliteStatement<'c>,
statement: std::marker::PhantomData<&'c ()>,
}

View File

@ -1,6 +1,7 @@
use std::borrow::Cow;
use std::convert::{TryFrom, TryInto};
#[derive(Debug)]
pub struct Url(url::Url);
impl TryFrom<String> for Url {
@ -28,6 +29,10 @@ impl<'s> TryFrom<&'s String> for Url {
}
impl Url {
pub(crate) fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn host(&self) -> &str {
let host = self.0.host_str();

View File

@ -32,6 +32,10 @@ pub use sqlx_core::mysql::{self, MySql, MySqlConnection, MySqlPool};
#[cfg_attr(docsrs, doc(cfg(feature = "postgres")))]
pub use sqlx_core::postgres::{self, PgConnection, PgPool, Postgres};
#[cfg(feature = "sqlite")]
#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))]
pub use sqlx_core::sqlite::{self, SqliteConnection, SqlitePool, Sqlite};
#[cfg(feature = "macros")]
#[doc(hidden)]
pub extern crate sqlx_macros;
@ -75,4 +79,7 @@ pub mod prelude {
#[cfg(feature = "mysql")]
pub use super::mysql::MySqlQueryAs;
#[cfg(feature = "sqlite")]
pub use super::sqlite::SqliteQueryAs;
}

52
tests/sqlite.rs Normal file
View File

@ -0,0 +1,52 @@
use futures::TryStreamExt;
use sqlx::{sqlite::SqliteQueryAs, Connection, Executor, Sqlite, SqlitePool, SqliteConnection, Connect};
use sqlx_test::new;
use std::time::Duration;
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn it_connects() -> anyhow::Result<()> {
Ok(new::<Sqlite>().await?.ping().await?)
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn it_fails_to_connect() -> anyhow::Result<()> {
// empty connection string
assert!(SqliteConnection::connect("").await.is_err());
assert!(dbg!(SqliteConnection::connect("sqlite:///please_do_not_run_sqlx_tests_as_root").await).is_err());
Ok(())
}
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
async fn it_executes() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let _ = conn
.execute(
r#"
CREATE TEMPORARY TABLE users (id INTEGER PRIMARY KEY)
"#,
)
.await?;
for index in 1..=10_i32 {
let cnt = sqlx::query("INSERT INTO users (id) VALUES (?)")
.bind(index)
.execute(&mut conn)
.await?;
assert_eq!(cnt, 1);
}
let sum: i32 = sqlx::query_as("SELECT id FROM users")
.fetch(&mut conn)
.try_fold(0_i32, |acc, (x,): (i32,)| async move { Ok(acc + x) })
.await?;
assert_eq!(sum, 55);
Ok(())
}