use std::fmt::{self, Debug, Formatter};
use std::future::{self, Future};
use std::str::FromStr;
use std::time::Duration;
use log::LevelFilter;
use sqlx_core::connection::{ConnectOptions, Connection};
use sqlx_core::error::Error;
use sqlx_core::transaction::Transaction;
use sqlx_core::Url;
use crate::Do;
pub struct DoConnection {
pub(crate) storage: worker::Storage,
pub(crate) sql: worker::SqlStorage,
}
unsafe impl Send for DoConnection {}
unsafe impl Sync for DoConnection {}
impl DoConnection {
#[must_use]
pub fn new(storage: worker::Storage) -> Self {
let sql = storage.sql();
Self { storage, sql }
}
#[must_use]
pub fn storage(&self) -> &worker::Storage {
&self.storage
}
#[must_use]
pub fn sql(&self) -> &worker::SqlStorage {
&self.sql
}
#[must_use]
pub fn into_storage(self) -> worker::Storage {
self.storage
}
}
impl From<worker::Storage> for DoConnection {
fn from(storage: worker::Storage) -> Self {
Self::new(storage)
}
}
impl Debug for DoConnection {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("DoConnection").finish_non_exhaustive()
}
}
impl Connection for DoConnection {
type Database = Do;
type Options = DoConnectOptions;
fn close(self) -> impl Future<Output = Result<(), Error>> + Send + 'static {
future::ready(Ok(()))
}
fn close_hard(self) -> impl Future<Output = Result<(), Error>> + Send + 'static {
future::ready(Ok(()))
}
fn ping(&mut self) -> impl Future<Output = Result<(), Error>> + Send + '_ {
future::ready(Ok(()))
}
fn begin(&mut self) -> impl Future<Output = Result<Transaction<'_, Do>, Error>> + Send + '_ {
Transaction::begin(self, None)
}
fn shrink_buffers(&mut self) {}
fn flush(&mut self) -> impl Future<Output = Result<(), Error>> + Send + '_ {
future::ready(Ok(()))
}
fn should_flush(&self) -> bool {
false
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct DoConnectOptions;
impl FromStr for DoConnectOptions {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
let url = Url::parse(s).map_err(|error| Error::Configuration(error.into()))?;
Self::from_url(&url)
}
}
impl ConnectOptions for DoConnectOptions {
type Connection = DoConnection;
fn from_url(url: &Url) -> Result<Self, Error> {
if url.scheme() == "do" {
Ok(Self)
} else {
Err(Error::Configuration(
format!("expected a `do://` URL, got scheme `{}`", url.scheme()).into(),
))
}
}
fn to_url_lossy(&self) -> Url {
Url::parse("do://").expect("a constant URL")
}
fn connect(&self) -> impl Future<Output = Result<DoConnection, Error>> + Send + '_ {
future::ready(Err(Error::Configuration(
"a Durable Object connection cannot be opened from a URL; build it from the object's \
storage with `DoConnection::new(state.storage())`"
.into(),
)))
}
fn log_statements(self, _level: LevelFilter) -> Self {
self
}
fn log_slow_statements(self, _level: LevelFilter, _duration: Duration) -> Self {
self
}
}
#[cfg(test)]
mod tests {
use sqlx_core::connection::ConnectOptions;
use super::DoConnectOptions;
#[test]
fn parses_a_do_url() {
let options: DoConnectOptions = "do://".parse().unwrap();
assert_eq!(options.to_url_lossy().as_str(), "do://");
}
#[test]
fn rejects_other_schemes() {
assert!("sqlite://db".parse::<DoConnectOptions>().is_err());
assert!("d1://DB".parse::<DoConnectOptions>().is_err());
}
}