mirror of
https://github.com/launchbadge/sqlx.git
synced 2026-02-28 02:39:25 +00:00
refactor: start by moving chunks of core into a core2 module
This commit is contained in:
parent
5f3245d7f4
commit
bd013ae375
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -2089,6 +2089,16 @@ dependencies = [
|
||||
"whoami",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-core2"
|
||||
version = "0.4.0-beta.2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"sqlx-rt",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-example-mysql-todos"
|
||||
version = "0.1.0"
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
members = [
|
||||
".",
|
||||
"sqlx-core",
|
||||
"sqlx-core2",
|
||||
"sqlx-rt",
|
||||
"sqlx-macros",
|
||||
"sqlx-test",
|
||||
|
||||
@ -1,162 +0,0 @@
|
||||
//! Traits to represent a database driver.
|
||||
//!
|
||||
//! # Support
|
||||
//!
|
||||
//! ## Tier 1
|
||||
//!
|
||||
//! Tier 1 support can be thought of as "guaranteed to work". Automated testing is setup to
|
||||
//! ensure a high level of stability and functionality.
|
||||
//!
|
||||
//! | Database | Version | Driver |
|
||||
//! | - | - | - |
|
||||
//! | [MariaDB] | 10.1+ | [`mysql`] |
|
||||
//! | [Microsoft SQL Server] | 2019 | [`mssql`] |
|
||||
//! | [MySQL] | 5.6, 5.7, 8.0 | [`mysql`] |
|
||||
//! | [PostgreSQL] | 9.5+ | [`postgres`] |
|
||||
//! | [SQLite] | 3.20.1+ | [`sqlite`] |
|
||||
//!
|
||||
//! [MariaDB]: https://mariadb.com/
|
||||
//! [MySQL]: https://www.mysql.com/
|
||||
//! [Microsoft SQL Server]: https://www.microsoft.com/en-us/sql-server
|
||||
//! [PostgreSQL]: https://www.postgresql.org/
|
||||
//! [SQLite]: https://www.sqlite.org/
|
||||
//!
|
||||
//! [`mysql`]: ../sqlite/index.html
|
||||
//! [`postgres`]: ../postgres/index.html
|
||||
//! [`mssql`]: ../mssql/index.html
|
||||
//! [`sqlite`]: ../sqlite/index.html
|
||||
//!
|
||||
//! ## Tier 2
|
||||
//!
|
||||
//! Tier 2 support can be thought as "should work". No specific automated testing is done,
|
||||
//! at this time, but there are efforts to ensure compatibility. Tier 2 support also includes
|
||||
//! database distributions that provide protocols that closely match a database from Tier 1.
|
||||
//!
|
||||
//! _No databases are in tier 2 at this time._
|
||||
//!
|
||||
//! # `Any`
|
||||
//!
|
||||
//! Selecting a database driver is, by default, a compile-time decision. SQLx is designed this way
|
||||
//! to take full advantage of the performance and type safety made available by Rust.
|
||||
//!
|
||||
//! We recognize that you may wish to make a runtime decision to decide the database driver. The
|
||||
//! [`Any`] driver is provided for that purpose.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! // connect to SQLite
|
||||
//! let conn = AnyConnection::connect("sqlite://file.db").await?;
|
||||
//!
|
||||
//! // connect to Postgres, no code change
|
||||
//! // required, decided by the scheme of the URI
|
||||
//! let conn = AnyConnection::connect("postgres://localhost/sqlx").await?;
|
||||
//! ```
|
||||
//!
|
||||
//! [`Any`]: ../any/index.html
|
||||
//!
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::arguments::Arguments;
|
||||
use crate::column::Column;
|
||||
use crate::connection::Connection;
|
||||
use crate::done::Done;
|
||||
use crate::row::Row;
|
||||
use crate::statement::Statement;
|
||||
use crate::transaction::TransactionManager;
|
||||
use crate::type_info::TypeInfo;
|
||||
use crate::value::{Value, ValueRef};
|
||||
|
||||
/// A database driver.
|
||||
///
|
||||
/// This trait encapsulates a complete set of traits that implement a driver for a
|
||||
/// specific database (e.g., MySQL, PostgreSQL).
|
||||
pub trait Database:
|
||||
'static
|
||||
+ Sized
|
||||
+ Send
|
||||
+ Debug
|
||||
+ for<'r> HasValueRef<'r, Database = Self>
|
||||
+ for<'q> HasArguments<'q, Database = Self>
|
||||
+ for<'q> HasStatement<'q, Database = Self>
|
||||
{
|
||||
/// The concrete `Connection` implementation for this database.
|
||||
type Connection: Connection<Database = Self>;
|
||||
|
||||
/// The concrete `TransactionManager` implementation for this database.
|
||||
type TransactionManager: TransactionManager<Database = Self>;
|
||||
|
||||
/// The concrete `Row` implementation for this database.
|
||||
type Row: Row<Database = Self>;
|
||||
|
||||
/// The concrete `Done` implementation for this database.
|
||||
type Done: Done<Database = Self>;
|
||||
|
||||
/// The concrete `Column` implementation for this database.
|
||||
type Column: Column<Database = Self>;
|
||||
|
||||
/// The concrete `TypeInfo` implementation for this database.
|
||||
type TypeInfo: TypeInfo;
|
||||
|
||||
/// The concrete type used to hold an owned copy of the not-yet-decoded value that was
|
||||
/// received from the database.
|
||||
type Value: Value<Database = Self> + 'static;
|
||||
}
|
||||
|
||||
/// Associate [`Database`] with a [`ValueRef`](crate::value::ValueRef) of a generic lifetime.
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// The upcoming Rust feature, [Generic Associated Types], should obviate
|
||||
/// the need for this trait.
|
||||
///
|
||||
/// [`Database`]: trait.Database.html
|
||||
/// [Generic Associated Types]: https://github.com/rust-lang/rust/issues/44265
|
||||
pub trait HasValueRef<'r> {
|
||||
type Database: Database;
|
||||
|
||||
/// The concrete type used to hold a reference to the not-yet-decoded value that has just been
|
||||
/// received from the database.
|
||||
type ValueRef: ValueRef<'r, Database = Self::Database>;
|
||||
}
|
||||
|
||||
/// Associate [`Database`] with an [`Arguments`](crate::arguments::Arguments) of a generic lifetime.
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// The upcoming Rust feature, [Generic Associated Types], should obviate
|
||||
/// the need for this trait.
|
||||
///
|
||||
/// [`Database`]: trait.Database.html
|
||||
/// [Generic Associated Types]: https://github.com/rust-lang/rust/issues/44265
|
||||
pub trait HasArguments<'q> {
|
||||
type Database: Database;
|
||||
|
||||
/// The concrete `Arguments` implementation for this database.
|
||||
type Arguments: Arguments<'q, Database = Self::Database>;
|
||||
|
||||
/// The concrete type used as a buffer for arguments while encoding.
|
||||
type ArgumentBuffer;
|
||||
}
|
||||
|
||||
/// Associate [`Database`] with a [`Statement`](crate::statement::Statement) of a generic lifetime.
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// The upcoming Rust feature, [Generic Associated Types], should obviate
|
||||
/// the need for this trait.
|
||||
///
|
||||
/// [`Database`]: trait.Database.html
|
||||
/// [Generic Associated Types]: https://github.com/rust-lang/rust/issues/44265
|
||||
pub trait HasStatement<'q> {
|
||||
type Database: Database;
|
||||
|
||||
/// The concrete `Statement` implementation for this database.
|
||||
type Statement: Statement<'q, Database = Self::Database>;
|
||||
}
|
||||
|
||||
/// A [`Database`] that maintains a client-side cache of prepared statements.
|
||||
///
|
||||
/// [`Database`]: trait.Database.html
|
||||
pub trait HasStatementCache {}
|
||||
@ -1,246 +0,0 @@
|
||||
//! Types for working with errors produced by SQLx.
|
||||
|
||||
use std::any::type_name;
|
||||
use std::borrow::Cow;
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt::Display;
|
||||
use std::io;
|
||||
use std::result::Result as StdResult;
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::type_info::TypeInfo;
|
||||
use crate::types::Type;
|
||||
|
||||
/// A specialized `Result` type for SQLx.
|
||||
pub type Result<T> = StdResult<T, Error>;
|
||||
|
||||
// Convenience type alias for usage within SQLx.
|
||||
// Do not make this type public.
|
||||
pub type BoxDynError = Box<dyn StdError + 'static + Send + Sync>;
|
||||
|
||||
/// An unexpected `NULL` was encountered during decoding.
|
||||
///
|
||||
/// Returned from [`Row::get`](sqlx_core::row::Row::get) if the value from the database is `NULL`,
|
||||
/// and you are not decoding into an `Option`.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[error("unexpected null; try decoding as an `Option`")]
|
||||
pub struct UnexpectedNullError;
|
||||
|
||||
/// Represents all the ways a method can fail within SQLx.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
/// Error occurred while parsing a connection string.
|
||||
#[error("error with configuration: {0}")]
|
||||
Configuration(#[source] BoxDynError),
|
||||
|
||||
/// Error returned from the database.
|
||||
#[error("error returned from database: {0}")]
|
||||
Database(Box<dyn DatabaseError>),
|
||||
|
||||
/// Error communicating with the database backend.
|
||||
#[error("error communicating with the server: {0}")]
|
||||
Io(#[from] io::Error),
|
||||
|
||||
/// Error occurred while attempting to establish a TLS connection.
|
||||
#[error("error occurred while attempting to establish a TLS connection: {0}")]
|
||||
Tls(#[source] BoxDynError),
|
||||
|
||||
/// Unexpected or invalid data encountered while communicating with the database.
|
||||
///
|
||||
/// This should indicate there is a programming error in a SQLx driver or there
|
||||
/// is something corrupted with the connection to the database itself.
|
||||
#[error("encountered unexpected or invalid data: {0}")]
|
||||
Protocol(String),
|
||||
|
||||
/// No rows returned by a query that expected to return at least one row.
|
||||
#[error("no rows returned by a query that expected to return at least one row")]
|
||||
RowNotFound,
|
||||
|
||||
/// Column index was out of bounds.
|
||||
#[error("column index out of bounds: the len is {len}, but the index is {index}")]
|
||||
ColumnIndexOutOfBounds { index: usize, len: usize },
|
||||
|
||||
/// No column found for the given name.
|
||||
#[error("no column found for name: {0}")]
|
||||
ColumnNotFound(String),
|
||||
|
||||
/// Error occurred while decoding a value from a specific column.
|
||||
#[error("error occurred while decoding column {index}: {source}")]
|
||||
ColumnDecode {
|
||||
index: String,
|
||||
|
||||
#[source]
|
||||
source: BoxDynError,
|
||||
},
|
||||
|
||||
/// Error occurred while decoding a value.
|
||||
#[error("error occurred while decoding: {0}")]
|
||||
Decode(#[source] BoxDynError),
|
||||
|
||||
/// A [`Pool::acquire`] timed out due to connections not becoming available or
|
||||
/// because another task encountered too many errors while trying to open a new connection.
|
||||
///
|
||||
/// [`Pool::acquire`]: crate::pool::Pool::acquire
|
||||
#[error("pool timed out while waiting for an open connection")]
|
||||
PoolTimedOut,
|
||||
|
||||
/// [`Pool::close`] was called while we were waiting in [`Pool::acquire`].
|
||||
///
|
||||
/// [`Pool::acquire`]: crate::pool::Pool::acquire
|
||||
/// [`Pool::close`]: crate::pool::Pool::close
|
||||
#[error("attempted to acquire a connection on a closed pool")]
|
||||
PoolClosed,
|
||||
|
||||
#[cfg(feature = "migrate")]
|
||||
#[error("{0}")]
|
||||
Migrate(#[source] Box<crate::migrate::MigrateError>),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn into_database_error(self) -> Option<Box<dyn DatabaseError + 'static>> {
|
||||
match self {
|
||||
Error::Database(err) => Some(err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_database_error(&self) -> Option<&(dyn DatabaseError + 'static)> {
|
||||
match self {
|
||||
Error::Database(err) => Some(&**err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
pub(crate) fn protocol(err: impl Display) -> Self {
|
||||
Error::Protocol(err.to_string())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
pub(crate) fn config(err: impl StdError + Send + Sync + 'static) -> Self {
|
||||
Error::Configuration(err.into())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
pub(crate) fn tls(err: impl StdError + Send + Sync + 'static) -> Self {
|
||||
Error::Tls(err.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn mismatched_types<DB: Database, T: Type<DB>>(ty: &DB::TypeInfo) -> BoxDynError {
|
||||
// TODO: `#name` only produces `TINYINT` but perhaps we want to show `TINYINT(1)`
|
||||
format!(
|
||||
"mismatched types; Rust type `{}` (as SQL type `{}`) is not compatible with SQL type `{}`",
|
||||
type_name::<T>(),
|
||||
T::type_info().name(),
|
||||
ty.name()
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// An error that was returned from the database.
|
||||
pub trait DatabaseError: 'static + Send + Sync + StdError {
|
||||
/// The primary, human-readable error message.
|
||||
fn message(&self) -> &str;
|
||||
|
||||
/// The (SQLSTATE) code for the error.
|
||||
fn code(&self) -> Option<Cow<'_, str>> {
|
||||
None
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static);
|
||||
|
||||
#[doc(hidden)]
|
||||
fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static);
|
||||
|
||||
#[doc(hidden)]
|
||||
fn into_error(self: Box<Self>) -> Box<dyn StdError + Send + Sync + 'static>;
|
||||
}
|
||||
|
||||
impl dyn DatabaseError {
|
||||
/// Downcast a reference to this generic database error to a specific
|
||||
/// database error type.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the database error type is not `E`. This is a deliberate contrast from
|
||||
/// `Error::downcast_ref` which returns `Option<&E>`. In normal usage, you should know the
|
||||
/// specific error type. In other cases, use `try_downcast_ref`.
|
||||
///
|
||||
pub fn downcast_ref<E: DatabaseError>(&self) -> &E {
|
||||
self.try_downcast_ref().unwrap_or_else(|| {
|
||||
panic!(
|
||||
"downcast to wrong DatabaseError type; original error: {}",
|
||||
self
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Downcast this generic database error to a specific database error type.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the database error type is not `E`. This is a deliberate contrast from
|
||||
/// `Error::downcast` which returns `Option<E>`. In normal usage, you should know the
|
||||
/// specific error type. In other cases, use `try_downcast`.
|
||||
///
|
||||
pub fn downcast<E: DatabaseError>(self: Box<Self>) -> Box<E> {
|
||||
self.try_downcast().unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"downcast to wrong DatabaseError type; original error: {}",
|
||||
e
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Downcast a reference to this generic database error to a specific
|
||||
/// database error type.
|
||||
#[inline]
|
||||
pub fn try_downcast_ref<E: DatabaseError>(&self) -> Option<&E> {
|
||||
self.as_error().downcast_ref()
|
||||
}
|
||||
|
||||
/// Downcast this generic database error to a specific database error type.
|
||||
#[inline]
|
||||
pub fn try_downcast<E: DatabaseError>(self: Box<Self>) -> StdResult<Box<E>, Box<Self>> {
|
||||
if self.as_error().is::<E>() {
|
||||
Ok(self.into_error().downcast().unwrap())
|
||||
} else {
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> From<E> for Error
|
||||
where
|
||||
E: DatabaseError,
|
||||
{
|
||||
#[inline]
|
||||
fn from(error: E) -> Self {
|
||||
Error::Database(Box::new(error))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "migrate")]
|
||||
impl From<crate::migrate::MigrateError> for Error {
|
||||
#[inline]
|
||||
fn from(error: crate::migrate::MigrateError) -> Self {
|
||||
Error::Migrate(Box::new(error))
|
||||
}
|
||||
}
|
||||
|
||||
// Format an error message as a `Protocol` error
|
||||
macro_rules! err_protocol {
|
||||
($expr:expr) => {
|
||||
$crate::error::Error::Protocol($expr.into())
|
||||
};
|
||||
|
||||
($fmt:expr, $($arg:tt)*) => {
|
||||
$crate::error::Error::Protocol(format!($fmt, $($arg)*))
|
||||
};
|
||||
}
|
||||
@ -1,136 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::io;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use sqlx_rt::{AsyncRead, AsyncReadExt, AsyncWrite};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::io::write_and_flush::WriteAndFlush;
|
||||
use crate::io::{decode::Decode, encode::Encode};
|
||||
use std::io::Cursor;
|
||||
|
||||
pub struct BufStream<S>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
stream: S,
|
||||
|
||||
// writes with `write` to the underlying stream are buffered
|
||||
// this can be flushed with `flush`
|
||||
pub(crate) wbuf: Vec<u8>,
|
||||
|
||||
// we read into the read buffer using 100% safe code
|
||||
rbuf: BytesMut,
|
||||
}
|
||||
|
||||
impl<S> BufStream<S>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
pub fn new(stream: S) -> Self {
|
||||
Self {
|
||||
stream,
|
||||
wbuf: Vec::with_capacity(512),
|
||||
rbuf: BytesMut::with_capacity(4096),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write<'en, T>(&mut self, value: T)
|
||||
where
|
||||
T: Encode<'en, ()>,
|
||||
{
|
||||
self.write_with(value, ())
|
||||
}
|
||||
|
||||
pub fn write_with<'en, T, C>(&mut self, value: T, context: C)
|
||||
where
|
||||
T: Encode<'en, C>,
|
||||
{
|
||||
value.encode_with(&mut self.wbuf, context);
|
||||
}
|
||||
|
||||
pub fn flush(&mut self) -> WriteAndFlush<'_, S> {
|
||||
WriteAndFlush {
|
||||
stream: &mut self.stream,
|
||||
buf: Cursor::new(&mut self.wbuf),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn read<'de, T>(&mut self, cnt: usize) -> Result<T, Error>
|
||||
where
|
||||
T: Decode<'de, ()>,
|
||||
{
|
||||
self.read_with(cnt, ()).await
|
||||
}
|
||||
|
||||
pub async fn read_with<'de, T, C>(&mut self, cnt: usize, context: C) -> Result<T, Error>
|
||||
where
|
||||
T: Decode<'de, C>,
|
||||
{
|
||||
T::decode_with(self.read_raw(cnt).await?.freeze(), context)
|
||||
}
|
||||
|
||||
pub async fn read_raw(&mut self, cnt: usize) -> Result<BytesMut, Error> {
|
||||
read_raw_into(&mut self.stream, &mut self.rbuf, cnt).await?;
|
||||
let buf = self.rbuf.split_to(cnt);
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
pub async fn read_raw_into(&mut self, buf: &mut BytesMut, cnt: usize) -> Result<(), Error> {
|
||||
read_raw_into(&mut self.stream, buf, cnt).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Deref for BufStream<S>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
type Target = S;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.stream
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> DerefMut for BufStream<S>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.stream
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_raw_into<S: AsyncRead + Unpin>(
|
||||
stream: &mut S,
|
||||
buf: &mut BytesMut,
|
||||
cnt: usize,
|
||||
) -> Result<(), Error> {
|
||||
let offset = buf.len();
|
||||
|
||||
// zero-fills the space in the read buffer
|
||||
buf.resize(offset + cnt, 0);
|
||||
|
||||
let mut read = offset;
|
||||
while (offset + cnt) > read {
|
||||
// read in bytes from the stream into the read buffer starting
|
||||
// from the offset we last read from
|
||||
let n = stream.read(&mut buf[read..]).await?;
|
||||
|
||||
if n == 0 {
|
||||
// a zero read when we had space in the read buffer
|
||||
// should be treated as an EOF
|
||||
|
||||
// and an unexpected EOF means the server told us to go away
|
||||
|
||||
return Err(io::Error::from(io::ErrorKind::ConnectionAborted).into());
|
||||
}
|
||||
|
||||
read += n;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
use bytes::Bytes;
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
pub trait Decode<'de, Context = ()>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn decode(buf: Bytes) -> Result<Self, Error>
|
||||
where
|
||||
Self: Decode<'de, ()>,
|
||||
{
|
||||
Self::decode_with(buf, ())
|
||||
}
|
||||
|
||||
fn decode_with(buf: Bytes, context: Context) -> Result<Self, Error>;
|
||||
}
|
||||
|
||||
impl Decode<'_> for Bytes {
|
||||
fn decode_with(buf: Bytes, _: ()) -> Result<Self, Error> {
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_> for () {
|
||||
fn decode_with(_: Bytes, _: ()) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
pub trait Encode<'en, Context = ()> {
|
||||
fn encode(&self, buf: &mut Vec<u8>)
|
||||
where
|
||||
Self: Encode<'en, ()>,
|
||||
{
|
||||
self.encode_with(buf, ());
|
||||
}
|
||||
|
||||
fn encode_with(&self, buf: &mut Vec<u8>, context: Context);
|
||||
}
|
||||
|
||||
impl<'en, C> Encode<'en, C> for &'_ [u8] {
|
||||
fn encode_with(&self, buf: &mut Vec<u8>, _: C) {
|
||||
buf.extend_from_slice(self);
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,5 @@
|
||||
mod buf;
|
||||
mod buf_mut;
|
||||
mod buf_stream;
|
||||
mod decode;
|
||||
mod encode;
|
||||
mod write_and_flush;
|
||||
|
||||
pub use buf::BufExt;
|
||||
pub use buf_mut::BufMutExt;
|
||||
pub use buf_stream::BufStream;
|
||||
pub use decode::Decode;
|
||||
pub use encode::Encode;
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
use crate::error::Error;
|
||||
use futures_core::Future;
|
||||
use futures_util::ready;
|
||||
use sqlx_rt::AsyncWrite;
|
||||
use std::io::{BufRead, Cursor};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
// Atomic operation that writes the full buffer to the stream, flushes the stream, and then
|
||||
// clears the buffer (even if either of the two previous operations failed).
|
||||
pub struct WriteAndFlush<'a, S> {
|
||||
pub(super) stream: &'a mut S,
|
||||
pub(super) buf: Cursor<&'a mut Vec<u8>>,
|
||||
}
|
||||
|
||||
impl<S: AsyncWrite + Unpin> Future for WriteAndFlush<'_, S> {
|
||||
type Output = Result<(), Error>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let Self {
|
||||
ref mut stream,
|
||||
ref mut buf,
|
||||
} = *self;
|
||||
|
||||
loop {
|
||||
let read = buf.fill_buf()?;
|
||||
|
||||
if !read.is_empty() {
|
||||
let written = ready!(Pin::new(&mut *stream).poll_write(cx, read)?);
|
||||
buf.consume(written);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Pin::new(stream).poll_flush(cx).map_err(Error::Io)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S> Drop for WriteAndFlush<'a, S> {
|
||||
fn drop(&mut self) {
|
||||
// clear the buffer regardless of whether the flush succeeded or not
|
||||
self.buf.get_mut().clear();
|
||||
}
|
||||
}
|
||||
21
sqlx-core2/Cargo.toml
Normal file
21
sqlx-core2/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "sqlx-core2"
|
||||
version = "0.4.0-beta.2"
|
||||
edition = "2018"
|
||||
authors = [
|
||||
"Ryan Leckey <leckey.ryan@gmail.com>",
|
||||
"Austin Bonander <austin.bonander@gmail.com>",
|
||||
"Chloe Ross <orangesnowfox@gmail.com>",
|
||||
"Daniel Akhterov <akhterovd@gmail.com>",
|
||||
]
|
||||
|
||||
[features]
|
||||
runtime-async-std = [ "sqlx-rt/runtime-async-std" ]
|
||||
runtime-tokio = [ "sqlx-rt/runtime-tokio" ]
|
||||
runtime-actix = [ "sqlx-rt/runtime-actix" ]
|
||||
|
||||
[dependencies]
|
||||
bytes = "0.5.6"
|
||||
thiserror = "1.0.20"
|
||||
sqlx-rt = { path = "../sqlx-rt", version = "0.1.1" }
|
||||
futures-core = "0.3.5"
|
||||
@ -1,10 +1,11 @@
|
||||
use crate::database::{Database, HasStatementCache};
|
||||
//! Provides the [`Connection`] trait to represent a single database connection.
|
||||
use crate::database::HasStatementCache;
|
||||
use crate::error::Error;
|
||||
use crate::transaction::Transaction;
|
||||
use crate::{database::Database, options::ConnectOptions};
|
||||
use futures_core::future::BoxFuture;
|
||||
use futures_core::Future;
|
||||
use std::fmt::Debug;
|
||||
use std::str::FromStr;
|
||||
|
||||
// TODO: Connection#transaction()
|
||||
// TODO: Connection#begin()
|
||||
|
||||
/// Represents a single database connection.
|
||||
pub trait Connection: Send {
|
||||
@ -22,46 +23,6 @@ pub trait Connection: Send {
|
||||
/// Checks if a connection to the database is still valid.
|
||||
fn ping(&mut self) -> BoxFuture<'_, Result<(), Error>>;
|
||||
|
||||
/// Begin a new transaction or establish a savepoint within the active transaction.
|
||||
///
|
||||
/// Returns a [`Transaction`] for controlling and tracking the new transaction.
|
||||
fn begin(&mut self) -> BoxFuture<'_, Result<Transaction<'_, Self::Database>, Error>>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Execute the function inside a transaction.
|
||||
///
|
||||
/// If the function returns an error, the transaction will be rolled back. If it does not
|
||||
/// return an error, the transaction will be committed.
|
||||
fn transaction<'c: 'f, 'f, T, E, F, Fut>(&'c mut self, f: F) -> BoxFuture<'f, Result<T, E>>
|
||||
where
|
||||
Self: Sized,
|
||||
T: Send,
|
||||
F: FnOnce(&mut <Self::Database as Database>::Connection) -> Fut + Send + 'f,
|
||||
E: From<Error> + Send,
|
||||
Fut: Future<Output = Result<T, E>> + Send,
|
||||
{
|
||||
Box::pin(async move {
|
||||
let mut tx = self.begin().await?;
|
||||
|
||||
match f(&mut tx).await {
|
||||
Ok(r) => {
|
||||
// no error occurred, commit the transaction
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
// an error occurred, rollback the transaction
|
||||
tx.rollback().await?;
|
||||
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// The number of statements currently cached in the connection.
|
||||
fn cached_statements_size(&self) -> usize
|
||||
where
|
||||
@ -107,12 +68,3 @@ pub trait Connection: Send {
|
||||
options.connect()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ConnectOptions: 'static + Send + Sync + FromStr<Err = Error> + Debug {
|
||||
type Connection: Connection + ?Sized;
|
||||
|
||||
/// Establish a new database connection with the options specified by `self`.
|
||||
fn connect(&self) -> BoxFuture<'_, Result<Self::Connection, Error>>
|
||||
where
|
||||
Self::Connection: Sized;
|
||||
}
|
||||
157
sqlx-core2/src/database.rs
Normal file
157
sqlx-core2/src/database.rs
Normal file
@ -0,0 +1,157 @@
|
||||
//! Provides the [`Database`] trait and other associated traits to represent a database driver.
|
||||
//!
|
||||
//! # Support
|
||||
//!
|
||||
//! ## Tier 1
|
||||
//!
|
||||
//! Tier 1 support can be thought of as "guaranteed to work". Automated testing is setup to
|
||||
//! ensure a high level of stability and functionality.
|
||||
//!
|
||||
//! | Database | Version | Driver |
|
||||
//! | - | - | - |
|
||||
//! | [MariaDB] | 10.1+ | [`mysql`] |
|
||||
//! | [Microsoft SQL Server] | 2019 | [`mssql`] |
|
||||
//! | [MySQL] | 5.6, 5.7, 8.0 | [`mysql`] |
|
||||
//! | [PostgreSQL] | 9.5+ | [`postgres`] |
|
||||
//! | [SQLite] | 3.20.1+ | [`sqlite`] |
|
||||
//!
|
||||
//! [MariaDB]: https://mariadb.com/
|
||||
//! [MySQL]: https://www.mysql.com/
|
||||
//! [Microsoft SQL Server]: https://www.microsoft.com/en-us/sql-server
|
||||
//! [PostgreSQL]: https://www.postgresql.org/
|
||||
//! [SQLite]: https://www.sqlite.org/
|
||||
//!
|
||||
//! [`mysql`]: ../sqlite/index.html
|
||||
//! [`postgres`]: ../postgres/index.html
|
||||
//! [`mssql`]: ../mssql/index.html
|
||||
//! [`sqlite`]: ../sqlite/index.html
|
||||
//!
|
||||
//! ## Tier 2
|
||||
//!
|
||||
//! Tier 2 support can be thought as "should work". No specific automated testing is done,
|
||||
//! at this time, but there are efforts to ensure compatibility. Tier 2 support also includes
|
||||
//! database distributions that provide protocols that closely match a database from Tier 1.
|
||||
//!
|
||||
//! _No databases are in tier 2 at this time._
|
||||
//!
|
||||
//! # `Any`
|
||||
//!
|
||||
//! Selecting a database driver is, by default, a compile-time decision. SQLx is designed this way
|
||||
//! to take full advantage of the performance and type safety made available by Rust.
|
||||
//!
|
||||
//! We recognize that you may wish to make a runtime decision to decide the database driver. The
|
||||
//! [`Any`] driver is provided for that purpose.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! // connect to SQLite
|
||||
//! let conn = AnyConnection::connect("sqlite://file.db").await?;
|
||||
//!
|
||||
//! // connect to Postgres, no code change
|
||||
//! // required, decided by the scheme of the URI
|
||||
//! let conn = AnyConnection::connect("postgres://localhost/sqlx").await?;
|
||||
//! ```
|
||||
//!
|
||||
//! [`Any`]: ../any/index.html
|
||||
//!
|
||||
|
||||
// use crate::arguments::Arguments;
|
||||
// use crate::column::Column;
|
||||
use crate::connection::Connection;
|
||||
// use crate::done::Done;
|
||||
// use crate::row::Row;
|
||||
// use crate::statement::Statement;
|
||||
// use crate::transaction::TransactionManager;
|
||||
// use crate::type_info::TypeInfo;
|
||||
// use crate::value::{Value, ValueRef};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// A database driver.
|
||||
///
|
||||
/// This trait encapsulates a complete set of traits that implement a driver for a
|
||||
/// specific database (e.g., MySQL, PostgreSQL).
|
||||
pub trait Database: 'static + Sized + Send + Debug
|
||||
// + for<'r> HasValueRef<'r, Database = Self>
|
||||
// + for<'q> HasArguments<'q, Database = Self>
|
||||
// + for<'q> HasStatement<'q, Database = Self>
|
||||
{
|
||||
/// The concrete `Connection` implementation for this database.
|
||||
type Connection: Connection<Database = Self>;
|
||||
|
||||
// /// The concrete `TransactionManager` implementation for this database.
|
||||
// type TransactionManager: TransactionManager<Database = Self>;
|
||||
|
||||
// /// The concrete `Row` implementation for this database.
|
||||
// type Row: Row<Database = Self>;
|
||||
//
|
||||
// /// The concrete `Done` implementation for this database.
|
||||
// type Done: Done<Database = Self>;
|
||||
//
|
||||
// /// The concrete `Column` implementation for this database.
|
||||
// type Column: Column<Database = Self>;
|
||||
//
|
||||
// /// The concrete `TypeInfo` implementation for this database.
|
||||
// type TypeInfo: TypeInfo;
|
||||
|
||||
// /// The concrete type used to hold an owned copy of the not-yet-decoded value that was
|
||||
// /// received from the database.
|
||||
// type Value: Value<Database = Self> + 'static;
|
||||
}
|
||||
|
||||
// /// Associate [`Database`] with a [`ValueRef`](crate::value::ValueRef) of a generic lifetime.
|
||||
// ///
|
||||
// /// ---
|
||||
// ///
|
||||
// /// The upcoming Rust feature, [Generic Associated Types], should obviate
|
||||
// /// the need for this trait.
|
||||
// ///
|
||||
// /// [`Database`]: trait.Database.html
|
||||
// /// [Generic Associated Types]: https://github.com/rust-lang/rust/issues/44265
|
||||
// pub trait HasValueRef<'r> {
|
||||
// type Database: Database;
|
||||
//
|
||||
// /// The concrete type used to hold a reference to the not-yet-decoded value that has just been
|
||||
// /// received from the database.
|
||||
// type ValueRef: ValueRef<'r, Database = Self::Database>;
|
||||
// }
|
||||
|
||||
// /// Associate [`Database`] with an [`Arguments`](crate::arguments::Arguments) of a generic lifetime.
|
||||
// ///
|
||||
// /// ---
|
||||
// ///
|
||||
// /// The upcoming Rust feature, [Generic Associated Types], should obviate
|
||||
// /// the need for this trait.
|
||||
// ///
|
||||
// /// [`Database`]: trait.Database.html
|
||||
// /// [Generic Associated Types]: https://github.com/rust-lang/rust/issues/44265
|
||||
// pub trait HasArguments<'q> {
|
||||
// type Database: Database;
|
||||
//
|
||||
// /// The concrete `Arguments` implementation for this database.
|
||||
// type Arguments: Arguments<'q, Database = Self::Database>;
|
||||
//
|
||||
// /// The concrete type used as a buffer for arguments while encoding.
|
||||
// type ArgumentBuffer;
|
||||
// }
|
||||
//
|
||||
// /// Associate [`Database`] with a [`Statement`](crate::statement::Statement) of a generic lifetime.
|
||||
// ///
|
||||
// /// ---
|
||||
// ///
|
||||
// /// The upcoming Rust feature, [Generic Associated Types], should obviate
|
||||
// /// the need for this trait.
|
||||
// ///
|
||||
// /// [`Database`]: trait.Database.html
|
||||
// /// [Generic Associated Types]: https://github.com/rust-lang/rust/issues/44265
|
||||
// pub trait HasStatement<'q> {
|
||||
// type Database: Database;
|
||||
//
|
||||
// /// The concrete `Statement` implementation for this database.
|
||||
// type Statement: Statement<'q, Database = Self::Database>;
|
||||
// }
|
||||
|
||||
/// A [`Database`] that maintains a client-side cache of prepared statements.
|
||||
///
|
||||
/// [`Database`]: trait.Database.html
|
||||
pub trait HasStatementCache {}
|
||||
79
sqlx-core2/src/error.rs
Normal file
79
sqlx-core2/src/error.rs
Normal file
@ -0,0 +1,79 @@
|
||||
//! Errors produced by SQLx.
|
||||
|
||||
use std::error::Error as StdError;
|
||||
|
||||
/// A boxed alias of [`std::error::Error`] used in the variants of [`Error`]
|
||||
/// to accept unknown error types.
|
||||
///
|
||||
/// Ideally, Rust would provide an error primitive such as `std::error` or a [boxed alias itself](https://github.com/rust-lang/rfcs/pull/2820).
|
||||
///
|
||||
pub type BoxStdError = Box<dyn StdError + Send + Sync>;
|
||||
|
||||
// TODO: #RowNotFound
|
||||
// TODO: #ColumnIndexOutOfBounds
|
||||
// TODO: #ColumnNotFound
|
||||
// TODO: #ColumnDecode -> #ColumnFromValue
|
||||
// TODO: #Decode -> #FromValue
|
||||
// TODO: #ToValue
|
||||
// TODO: #PoolTimedOut
|
||||
// TODO: #PoolClosed
|
||||
// TODO: #Migrate
|
||||
|
||||
/// Represents all the ways a method can fail within SQLx.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// Error occurred while parsing a connection string or otherwise resolving configuration.
|
||||
#[error("error with configuration: {0}")]
|
||||
Configuration(#[source] BoxStdError),
|
||||
|
||||
/// Error communicating with the database server.
|
||||
#[error("error communicating with the server: {0}")]
|
||||
Network(#[from] std::io::Error),
|
||||
|
||||
/// Unexpected or invalid data encountered while communicating with the database.
|
||||
///
|
||||
/// This should indicate there is a programming error in a SQLx driver or there
|
||||
/// is something corrupted with the connection to the database itself.
|
||||
#[error("encountered unexpected or invalid data: {0}")]
|
||||
Protocol(#[source] BoxStdError),
|
||||
|
||||
/// Invalid SQL query or arguments.
|
||||
///
|
||||
/// Typically we catch these kinds of errors at compile-time with Rust's type system. An example
|
||||
/// of when it can still occur is a query string that is too large. For instance, in PostgreSQL,
|
||||
/// query strings have a maximum size of `i32::MAX`.
|
||||
#[error("{0}")]
|
||||
Query(#[source] BoxStdError),
|
||||
|
||||
/// Error occurred while attempting to establish a TLS connection.
|
||||
#[error("error occurred while attempting to establish a TLS connection: {0}")]
|
||||
Tls(#[source] BoxStdError),
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl Error {
|
||||
#[inline]
|
||||
pub fn protocol(err: impl StdError + Send + Sync + 'static) -> Self {
|
||||
Error::Protocol(err.into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn protocol_msg(msg: impl Into<String>) -> Self {
|
||||
Error::Protocol(msg.into().into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn configuration(err: impl StdError + Send + Sync + 'static) -> Self {
|
||||
Error::Configuration(err.into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn configuration_msg(msg: impl Into<String>) -> Self {
|
||||
Error::Configuration(msg.into().into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tls(err: impl StdError + Send + Sync + 'static) -> Self {
|
||||
Error::Tls(err.into())
|
||||
}
|
||||
}
|
||||
124
sqlx-core2/src/io/buf_stream.rs
Normal file
124
sqlx-core2/src/io/buf_stream.rs
Normal file
@ -0,0 +1,124 @@
|
||||
use crate::error::Error;
|
||||
use crate::io::Encode;
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use sqlx_rt::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// A buffered I/O wrapper around an async read/write stream.
|
||||
pub struct BufStream<S> {
|
||||
inner: S,
|
||||
|
||||
// read buffer
|
||||
rbuf: BytesMut,
|
||||
|
||||
// write buffer
|
||||
wbuf: Vec<u8>,
|
||||
|
||||
// offset into the write buffer that a previous write operation has written to
|
||||
wbuf_offset: usize,
|
||||
}
|
||||
|
||||
impl<S> BufStream<S> {
|
||||
pub fn with_capacity(inner: S, read: usize, write: usize) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
rbuf: BytesMut::with_capacity(read),
|
||||
wbuf: Vec::with_capacity(write),
|
||||
wbuf_offset: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write<'en, T: Encode<'en>>(&mut self, packet: T) -> Result<(), Error> {
|
||||
// we must be sure to not call [write] directly after a dropped [flush]
|
||||
debug_assert_eq!(self.wbuf_offset, 0);
|
||||
|
||||
packet.encode(&mut self.wbuf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsyncRead + AsyncWrite + Unpin> BufStream<S> {
|
||||
pub async fn read(&mut self, offset: usize, n: usize) -> Result<Bytes, Error> {
|
||||
self.fill_buf(offset, n).await?;
|
||||
|
||||
if offset != 0 {
|
||||
// drop the bytes from 0 .. offset
|
||||
let _ = self.rbuf.split_to(offset);
|
||||
}
|
||||
|
||||
// and return the slice of `n` bytes
|
||||
Ok(self.rbuf.split_to(n).freeze())
|
||||
}
|
||||
|
||||
pub async fn peek(&mut self, offset: usize, n: usize) -> Result<&[u8], Error> {
|
||||
self.fill_buf(offset, n).await?;
|
||||
|
||||
Ok(&self.rbuf[offset..offset + n])
|
||||
}
|
||||
|
||||
async fn fill_buf(&mut self, offset: usize, n: usize) -> Result<(), Error> {
|
||||
// before waiting to receive data,
|
||||
// flush the write buffer (if needed)
|
||||
if !self.wbuf.is_empty() {
|
||||
self.flush().await?;
|
||||
}
|
||||
|
||||
while self.rbuf.len() < (offset + n) {
|
||||
// ensure that there is room in the read buffer; this does nothing if there is at
|
||||
// least 128 unwritten bytes in the buffer
|
||||
self.rbuf.reserve(n.max(128));
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe {
|
||||
// get a chunk of uninitialized memory to write to
|
||||
// this is UB if the Read impl of the stream reads the write buffer
|
||||
let b = self.rbuf.bytes_mut();
|
||||
let b = UnsafeSend(&mut *(b as *mut [MaybeUninit<u8>] as *mut [u8]));
|
||||
|
||||
// read as much as we can and return when the stream or our buffer is exhausted
|
||||
let n = self.inner.read(b.0).await?;
|
||||
|
||||
// [!] read more than the length of our buffer
|
||||
debug_assert!(n <= b.0.len());
|
||||
|
||||
// update the `len` of the read buffer
|
||||
self.rbuf.advance_mut(n);
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn flush(&mut self) -> Result<(), Error> {
|
||||
// write as much as we can each time and move the cursor as we write from the buffer
|
||||
// if _this_ future drops, offset will have a record of how much of the wbuf has
|
||||
// been written
|
||||
while self.wbuf_offset < self.wbuf.len() {
|
||||
self.wbuf_offset += self.inner.write(&self.wbuf[self.wbuf_offset..]).await?;
|
||||
}
|
||||
|
||||
// fully written buffer, move cursor back to the beginning
|
||||
self.wbuf_offset = 0;
|
||||
self.wbuf.clear();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct UnsafeSend<'a>(&'a mut [u8]);
|
||||
|
||||
// TODO? unsafe impl Send for UnsafeSend<'_> {}
|
||||
|
||||
impl<S> Deref for BufStream<S> {
|
||||
type Target = S;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> DerefMut for BufStream<S> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
23
sqlx-core2/src/io/decode.rs
Normal file
23
sqlx-core2/src/io/decode.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use crate::error::Error;
|
||||
use bytes::Bytes;
|
||||
|
||||
/// An object that can be decoded from a byte buffer.
|
||||
/// Context is optional metadata that is required to decode this object.
|
||||
pub trait Decode<'de, Context = ()>: Sized {
|
||||
#[inline]
|
||||
fn decode(buf: Bytes) -> Result<Self, Error>
|
||||
where
|
||||
Self: Decode<'de, ()>,
|
||||
{
|
||||
Self::decode_with(buf, ())
|
||||
}
|
||||
|
||||
fn decode_with(buf: Bytes, context: Context) -> Result<Self, Error>;
|
||||
}
|
||||
|
||||
impl<C> Decode<'_, C> for Bytes {
|
||||
#[inline]
|
||||
fn decode_with(buf: Bytes, _: C) -> Result<Self, Error> {
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
24
sqlx-core2/src/io/encode.rs
Normal file
24
sqlx-core2/src/io/encode.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use crate::error::Error;
|
||||
|
||||
/// An object that can be encoded to a byte buffer.
|
||||
/// Context is optional metadata that is required to encode this object.
|
||||
pub trait Encode<'en, Context = ()> {
|
||||
#[inline]
|
||||
fn encode(&self, buf: &mut Vec<u8>) -> Result<(), Error>
|
||||
where
|
||||
Self: Encode<'en, ()>,
|
||||
{
|
||||
self.encode_with(buf, ())
|
||||
}
|
||||
|
||||
fn encode_with(&self, buf: &mut Vec<u8>, context: Context) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
impl<C> Encode<'_, C> for &'_ [u8] {
|
||||
#[inline]
|
||||
fn encode_with(&self, buf: &mut Vec<u8>, _: C) -> Result<(), Error> {
|
||||
buf.extend_from_slice(self);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
9
sqlx-core2/src/io/mod.rs
Normal file
9
sqlx-core2/src/io/mod.rs
Normal file
@ -0,0 +1,9 @@
|
||||
//! Low-level I/O shared between database driver implementations.
|
||||
|
||||
mod buf_stream;
|
||||
mod decode;
|
||||
mod encode;
|
||||
|
||||
pub use buf_stream::BufStream;
|
||||
pub use decode::Decode;
|
||||
pub use encode::Encode;
|
||||
8
sqlx-core2/src/lib.rs
Normal file
8
sqlx-core2/src/lib.rs
Normal file
@ -0,0 +1,8 @@
|
||||
//! Core of SQLx, the rust SQL toolkit. Not intended to be used directly.
|
||||
#![deny(unsafe_code)]
|
||||
#![warn(future_incompatible, rust_2018_idioms, unreachable_pub)]
|
||||
pub mod connection;
|
||||
pub mod database;
|
||||
pub mod error;
|
||||
pub mod io;
|
||||
pub mod options;
|
||||
24
sqlx-core2/src/options.rs
Normal file
24
sqlx-core2/src/options.rs
Normal file
@ -0,0 +1,24 @@
|
||||
//! Provides the [`ConnectOptions`] trait for configuring a new connection.
|
||||
|
||||
use crate::connection::Connection;
|
||||
use crate::error::Error;
|
||||
use futures_core::future::BoxFuture;
|
||||
use std::fmt::Debug;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Connection options for configuring a new connection.
|
||||
///
|
||||
/// Can be parsed from a semi-universal connection URI format of the form:
|
||||
///
|
||||
/// ```text
|
||||
/// driver://user:pass@host:port/database?param1=value1¶m2=value2
|
||||
/// ```
|
||||
///
|
||||
pub trait ConnectOptions: 'static + Send + Sync + FromStr<Err = Error> + Debug {
|
||||
type Connection: Connection + ?Sized;
|
||||
|
||||
/// Establish a new database connection with the options specified by `self`.
|
||||
fn connect(&self) -> BoxFuture<'_, Result<Self::Connection, Error>>
|
||||
where
|
||||
Self::Connection: Sized;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user