mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-12-29 21:00:54 +00:00
sqlite: initial work in connection
This commit is contained in:
parent
5d042e35b1
commit
7ab07016da
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -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",
|
||||
|
||||
@ -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" ]
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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(()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>;
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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>,
|
||||
{
|
||||
|
||||
25
sqlx-core/src/sqlite/types/int.rs
Normal file
25
sqlx-core/src/sqlite/types/int.rs
Normal 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!()
|
||||
}
|
||||
}
|
||||
@ -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 {}
|
||||
|
||||
|
||||
17
sqlx-core/src/sqlite/value.rs
Normal file
17
sqlx-core/src/sqlite/value.rs
Normal 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 ()>,
|
||||
}
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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
52
tests/sqlite.rs
Normal 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(())
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user