diff --git a/Cargo.lock b/Cargo.lock index e0a60ef1..452c2439 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index facc6367..60483af4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" ] diff --git a/sqlx-core/Cargo.toml b/sqlx-core/Cargo.toml index 61859103..a63b1d62 100644 --- a/sqlx-core/Cargo.toml +++ b/sqlx-core/Cargo.toml @@ -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 } + +# +[dependencies.libsqlite3-sys] +version = "0.17.1" +optional = true +default-features = false +features = [ "pkg-config", "vcpkg", "bundled" ] [dev-dependencies] matches = "0.1.8" diff --git a/sqlx-core/src/runtime.rs b/sqlx-core/src/runtime.rs index 15918fbf..856eb10d 100644 --- a/sqlx-core/src/runtime.rs +++ b/sqlx-core/src/runtime.rs @@ -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, diff --git a/sqlx-core/src/sqlite/arguments.rs b/sqlx-core/src/sqlite/arguments.rs index 2fb4da38..850d4b57 100644 --- a/sqlx-core/src/sqlite/arguments.rs +++ b/sqlx-core/src/sqlite/arguments.rs @@ -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), - - Double(f64), - - Int(i64), -} +use crate::sqlite::value::SqliteArgumentValue; #[derive(Default)] pub struct SqliteArguments { - values: Vec, + values: Vec, } impl Arguments for SqliteArguments { diff --git a/sqlx-core/src/sqlite/connection.rs b/sqlx-core/src/sqlite/connection.rs index ca5e528c..bd4816b2 100644 --- a/sqlx-core/src/sqlite/connection.rs +++ b/sqlx-core/src/sqlite/connection.rs @@ -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, +} + +// 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]. +// +// +// +#[allow(unsafe_code)] +unsafe impl Send for SqliteConnection {} +#[allow(unsafe_code)] +unsafe impl Sync for SqliteConnection {} + +fn establish(url: crate::Result) -> crate::Result { + 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; + + // + #[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(url: T) -> BoxFuture<'static, crate::Result> @@ -13,8 +72,8 @@ impl Connect for SqliteConnection { T: TryInto, 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> { - //Box::pin(Executor::execute(self, "SELECT 1").map_ok(|_| ())) - todo!() + // For SQLite connections, PING does effectively nothing + Box::pin(future::ok(())) } } diff --git a/sqlx-core/src/sqlite/database.rs b/sqlx-core/src/sqlite/database.rs index b3921d1f..e3a7e467 100644 --- a/sqlx-core/src/sqlite/database.rs +++ b/sqlx-core/src/sqlite/database.rs @@ -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; + type RawBuffer = Vec; } -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>; } diff --git a/sqlx-core/src/sqlite/error.rs b/sqlx-core/src/sqlite/error.rs index aa5713ad..40ec657e 100644 --- a/sqlx-core/src/sqlite/error.rs +++ b/sqlx-core/src/sqlite/error.rs @@ -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 } } diff --git a/sqlx-core/src/sqlite/mod.rs b/sqlx-core/src/sqlite/mod.rs index 32ec2b87..7c4b8f66 100644 --- a/sqlx-core/src/sqlite/mod.rs +++ b/sqlx-core/src/sqlite/mod.rs @@ -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; + +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); diff --git a/sqlx-core/src/sqlite/row.rs b/sqlx-core/src/sqlite/row.rs index 655611f7..7ee86949 100644 --- a/sqlx-core/src/sqlite/row.rs +++ b/sqlx-core/src/sqlite/row.rs @@ -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, 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> + fn try_get_raw<'r, I>(&'r self, index: I) -> crate::Result> where I: ColumnIndex, { diff --git a/sqlx-core/src/sqlite/types/int.rs b/sqlx-core/src/sqlite/types/int.rs new file mode 100644 index 00000000..3a8f955c --- /dev/null +++ b/sqlx-core/src/sqlite/types/int.rs @@ -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 for i32 { + fn type_info() -> SqliteTypeInfo { + // SqliteTypeInfo::new(ValueKind::Int) + todo!() + } +} + +impl Encode for i32 { + fn encode(&self, values: &mut Vec) { + values.push(SqliteArgumentValue::Int((*self).into())); + } +} + +impl<'a> Decode<'a, Sqlite> for i32 { + fn decode(value: SqliteResultValue<'a>) -> crate::Result { + // Ok(value.int()) + todo!() + } +} diff --git a/sqlx-core/src/sqlite/types/mod.rs b/sqlx-core/src/sqlite/types/mod.rs index 85acabbe..c54ca380 100644 --- a/sqlx-core/src/sqlite/types/mod.rs +++ b/sqlx-core/src/sqlite/types/mod.rs @@ -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 {} diff --git a/sqlx-core/src/sqlite/value.rs b/sqlx-core/src/sqlite/value.rs new file mode 100644 index 00000000..6a7d9648 --- /dev/null +++ b/sqlx-core/src/sqlite/value.rs @@ -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), + + Double(f64), + + Int(i64), +} + +pub struct SqliteResultValue<'c> { + // statement: SqliteStatement<'c>, + statement: std::marker::PhantomData<&'c ()>, +} diff --git a/sqlx-core/src/url.rs b/sqlx-core/src/url.rs index 0a85d8fb..ac4ff280 100644 --- a/sqlx-core/src/url.rs +++ b/sqlx-core/src/url.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use std::convert::{TryFrom, TryInto}; +#[derive(Debug)] pub struct Url(url::Url); impl TryFrom 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(); diff --git a/src/lib.rs b/src/lib.rs index 1a31ba6e..f3315262 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; } diff --git a/tests/sqlite.rs b/tests/sqlite.rs new file mode 100644 index 00000000..0b48b2fe --- /dev/null +++ b/tests/sqlite.rs @@ -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::().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::().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(()) +}