sqlx_cloudflare_d1/
connection.rs1use std::fmt::{self, Debug, Formatter};
2use std::future::{self, Future};
3use std::str::FromStr;
4use std::time::Duration;
5
6use log::LevelFilter;
7use sqlx_core::connection::{ConnectOptions, Connection};
8use sqlx_core::error::Error;
9use sqlx_core::transaction::Transaction;
10use sqlx_core::Url;
11
12use crate::D1;
13
14pub struct D1Connection {
23 pub(crate) db: worker::D1Database,
27}
28
29impl D1Connection {
30 #[must_use]
32 pub fn new(db: worker::D1Database) -> Self {
33 Self { db }
34 }
35
36 pub fn from_env(env: &worker::Env, binding: &str) -> worker::Result<Self> {
42 env.d1(binding).map(Self::new)
43 }
44
45 #[must_use]
48 pub fn database(&self) -> &worker::D1Database {
49 &self.db
50 }
51
52 #[must_use]
55 pub fn into_database(self) -> worker::D1Database {
56 self.db
57 }
58}
59
60impl Debug for D1Connection {
61 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
62 f.debug_struct("D1Connection").finish_non_exhaustive()
63 }
64}
65
66impl Connection for D1Connection {
67 type Database = D1;
68
69 type Options = D1ConnectOptions;
70
71 fn close(self) -> impl Future<Output = Result<(), Error>> + Send + 'static {
73 future::ready(Ok(()))
74 }
75
76 fn close_hard(self) -> impl Future<Output = Result<(), Error>> + Send + 'static {
77 future::ready(Ok(()))
78 }
79
80 fn ping(&mut self) -> impl Future<Output = Result<(), Error>> + Send + '_ {
83 future::ready(Ok(()))
84 }
85
86 fn begin(&mut self) -> impl Future<Output = Result<Transaction<'_, D1>, Error>> + Send + '_ {
88 Transaction::begin(self, None)
89 }
90
91 fn shrink_buffers(&mut self) {}
92
93 fn flush(&mut self) -> impl Future<Output = Result<(), Error>> + Send + '_ {
94 future::ready(Ok(()))
95 }
96
97 fn should_flush(&self) -> bool {
98 false
99 }
100}
101
102#[derive(Debug, Clone, PartialEq, Eq)]
109pub struct D1ConnectOptions {
110 binding: String,
111}
112
113impl D1ConnectOptions {
114 #[must_use]
116 pub fn binding(&self) -> &str {
117 &self.binding
118 }
119}
120
121impl FromStr for D1ConnectOptions {
122 type Err = Error;
123
124 fn from_str(s: &str) -> Result<Self, Error> {
125 let url = Url::parse(s).map_err(|error| Error::Configuration(error.into()))?;
126 Self::from_url(&url)
127 }
128}
129
130impl ConnectOptions for D1ConnectOptions {
131 type Connection = D1Connection;
132
133 fn from_url(url: &Url) -> Result<Self, Error> {
134 if url.scheme() != "d1" {
135 return Err(Error::Configuration(
136 format!(
137 "expected a `d1://<binding>` URL, got scheme `{}`",
138 url.scheme()
139 )
140 .into(),
141 ));
142 }
143
144 match url.host_str() {
145 Some(binding) if !binding.is_empty() => Ok(Self {
146 binding: binding.to_owned(),
147 }),
148 _ => Err(Error::Configuration(
149 "expected a `d1://<binding>` URL with a binding name".into(),
150 )),
151 }
152 }
153
154 fn to_url_lossy(&self) -> Url {
155 Url::parse(&format!("d1://{}", self.binding)).expect("a binding name parsed from a URL")
156 }
157
158 fn connect(&self) -> impl Future<Output = Result<D1Connection, Error>> + Send + '_ {
159 future::ready(Err(Error::Configuration(
160 format!(
161 "a D1 connection cannot be opened from a URL; get the `{}` binding from the \
162 Worker's environment with `D1Connection::from_env`",
163 self.binding
164 )
165 .into(),
166 )))
167 }
168
169 fn log_statements(self, _level: LevelFilter) -> Self {
171 self
172 }
173
174 fn log_slow_statements(self, _level: LevelFilter, _duration: Duration) -> Self {
175 self
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use sqlx_core::connection::ConnectOptions;
182
183 use super::D1ConnectOptions;
184
185 #[test]
186 fn parses_a_binding_url() {
187 let options: D1ConnectOptions = "d1://DB".parse().unwrap();
188
189 assert_eq!(options.binding(), "DB");
192 assert_eq!(options.to_url_lossy().as_str(), "d1://DB");
193 }
194
195 #[test]
196 fn rejects_other_schemes_and_a_missing_binding() {
197 assert!("sqlite://DB".parse::<D1ConnectOptions>().is_err());
198 assert!("d1://".parse::<D1ConnectOptions>().is_err());
199 }
200}