Skip to main content

narwhal_core/
connection.rs

1// Trait definitions intentionally keep explicit `'a` lifetimes on the
2// dyn-safe sibling methods: every borrowed parameter shares the same
3// lifetime as the returned `BoxFuture`, which elision cannot express
4// (multi-input borrows would each get an independent anonymous
5// lifetime).
6#![allow(clippy::needless_lifetimes, clippy::elidable_lifetime_names)]
7
8use crate::future::BoxFuture;
9use std::future::Future;
10use std::path::PathBuf;
11
12use serde::{Deserialize, Serialize};
13
14use crate::cancel::DynCancelHandle;
15use crate::capabilities::Capabilities;
16use crate::error::Result;
17use crate::query_stream::QueryStream;
18use crate::schema::{QueryResult, Schema, SchemaCatalog, Table, TableSchema};
19use crate::stream::DynRowStream;
20use crate::value::Value;
21
22/// Visual accent colour applied to the TUI border + status bar when a
23/// connection is active. The intent is operational safety: prod = red,
24/// staging = yellow, dev = green. Six named colours so terminal
25/// compatibility is trivial — no hex / RGB to render-degrade.
26///
27/// Serialises as lowercase (`color = "red"`).
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "lowercase")]
30#[non_exhaustive]
31pub enum ConnectionColor {
32    Red,
33    Yellow,
34    Green,
35    Blue,
36    Magenta,
37    Cyan,
38}
39
40/// TLS/SSL mode for a database connection.
41///
42/// Mirrors the standard libpq `sslmode` parameter. Serialises as
43/// kebab-case in TOML (`"verify-full"`, `"verify-ca"`, etc.).
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
45#[serde(rename_all = "kebab-case")]
46#[non_exhaustive]
47pub enum SslMode {
48    Disable,
49    #[default]
50    Prefer,
51    Require,
52    VerifyCa,
53    VerifyFull,
54}
55
56/// Static metadata describing how to reach a database.
57///
58/// The credential itself is not stored here; it is retrieved separately from
59/// the configured credential store and passed to
60/// [`crate::DatabaseDriver::connect`] at runtime.
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct ConnectionConfig {
63    pub id: uuid::Uuid,
64    pub name: String,
65    pub driver: String,
66    pub params: ConnectionParams,
67}
68
69/// Driver-agnostic connection parameters.
70///
71/// Each driver decides which fields are required; unused fields remain
72/// `None`. Engine-specific tuning is expressed through [`Self::options`].
73///
74/// Marked `#[non_exhaustive]` so adding new optional fields
75/// (`color`, `confirm_writes`, `read_only`, future TLS knobs, …)
76/// is a non-breaking change. Construct with `..Default::default()`
77/// or via the public setter pattern.
78#[derive(Debug, Clone, Default, Serialize, Deserialize)]
79#[non_exhaustive]
80pub struct ConnectionParams {
81    pub host: Option<String>,
82    pub port: Option<u16>,
83    pub database: Option<String>,
84    pub username: Option<String>,
85    pub path: Option<String>,
86    /// Optional password material declared *in the configuration file*.
87    ///
88    /// v1.x stored passwords exclusively in the OS keyring (or fell
89    /// back to `~/.pgpass` / env vars). v2.0 accepts an
90    /// optional in-file value so users can express:
91    ///
92    /// * a literal password (discouraged, but supported for parity);
93    /// * an `${env:VAR}` placeholder, expanded by
94    /// `narwhal_config::interpolate` at load time — same vocabulary
95    /// as every other string field;
96    /// * a vault reference: `vault:hashicorp/<path>#<field>` or
97    /// `1password:op://Vault/Item/field`, resolved at connect time
98    /// by `narwhal_config::vault::VaultRegistry`.
99    ///
100    /// Resolution order at runtime is:
101    ///
102    /// 1. If `password` is present and parses as a vault reference →
103    /// the configured provider returns the secret.
104    /// 2. If `password` is present and is *not* a reference → it is
105    /// used verbatim (after env interpolation).
106    /// 3. Else, the keyring is consulted by `connection.id`.
107    /// 4. Else, the `~/.pgpass` / env-var fallback runs.
108    ///
109    /// The reference is stored as a plain `String` because that is
110    /// the on-disk shape; the resolved secret is held in
111    /// `secrecy::SecretString` from the moment the resolver runs.
112    #[serde(default, skip_serializing_if = "Option::is_none")]
113    pub password: Option<String>,
114    #[serde(default)]
115    pub options: std::collections::BTreeMap<String, String>,
116    /// TLS/SSL mode. Defaults to [`SslMode::Prefer`] for network drivers
117    /// and [`SslMode::Disable`] for file-local drivers (sqlite, duckdb).
118    #[serde(default)]
119    pub ssl_mode: SslMode,
120    /// Path to the CA/root certificate bundle (PEM format).
121    #[serde(default)]
122    pub ssl_root_cert: Option<PathBuf>,
123    /// Path to the client certificate (PEM format).
124    #[serde(default)]
125    pub ssl_cert: Option<PathBuf>,
126    /// Path to the client private key (PEM format).
127    #[serde(default)]
128    pub ssl_key: Option<PathBuf>,
129    /// Optional SSH tunnel. When `Some`, [`crate::ssh::SshTunnel::spawn`]
130    /// brings up a local-port-forward before the driver connects and
131    /// rewrites `host`/`port` to the loopback side of the tunnel.
132    #[serde(default, skip_serializing_if = "Option::is_none")]
133    pub ssh: Option<SshConfig>,
134    /// L36 #7: ordered list of shell commands executed before the
135    /// connection is opened. Each step's stdout can be captured into
136    /// a named variable and substituted into the remaining string
137    /// fields of [`ConnectionParams`] via `${preconnect:NAME}`
138    /// placeholders. The canonical use case is fetching a short-lived
139    /// password from a secrets manager (`vault kv get …`) or a
140    /// kubectl pod IP before the driver dials in.
141    #[serde(default, skip_serializing_if = "Vec::is_empty")]
142    pub pre_connect: Vec<PreConnectStep>,
143    /// optional accent colour for the TUI border + status
144    /// bar while this connection is active. `None` keeps the theme
145    /// default. Production users typically set `color = "red"` so
146    /// "am I on prod?" is answered by a glance at the screen edge.
147    #[serde(default, skip_serializing_if = "Option::is_none")]
148    pub color: Option<ConnectionColor>,
149    /// when `true`, mutating statements (`INSERT`, `UPDATE`,
150    /// `DELETE`, DDL, …) prompt for a confirmation modal before they
151    /// reach the driver. Bare reads run without confirmation.
152    /// Recommended on every connection that touches production data.
153    #[serde(default, skip_serializing_if = "is_false")]
154    pub confirm_writes: bool,
155    /// when `true`, the session is opened in driver-enforced
156    /// read-only mode (`SET default_transaction_read_only TO ON` on
157    /// PG, `PRAGMA query_only = ON` on `SQLite`, etc.) **and** the TUI
158    /// applies the same syntactic guard MCP uses
159    /// (`narwhal_sql::guard_read_only`) before each run. Either
160    /// layer rejecting the statement aborts it without driver round
161    /// trip.
162    #[serde(default, skip_serializing_if = "is_false")]
163    pub read_only: bool,
164}
165
166#[allow(clippy::trivially_copy_pass_by_ref)]
167const fn is_false(b: &bool) -> bool {
168    !*b
169}
170
171impl ConnectionParams {
172    /// Construct a [`ConnectionParams`] by mutating the default via
173    /// `f`. The canonical way to build a `ConnectionParams` from
174    /// outside the `narwhal-core` crate — the struct is marked
175    /// `#[non_exhaustive]` so struct-literal construction (including
176    /// functional update syntax `..Default::default()`) is forbidden.
177    ///
178    /// Minimal network connection:
179    ///
180    /// ```
181    /// use narwhal_core::ConnectionParams;
182    /// let p = ConnectionParams::with(|p| {
183    /// p.host = Some("db.local".into());
184    /// p.port = Some(5432);
185    /// });
186    /// assert_eq!(p.port, Some(5432));
187    /// ```
188    ///
189    /// Production-tagged connection with the v1.1 safety knobs:
190    ///
191    /// ```
192    /// use narwhal_core::{ConnectionColor, ConnectionParams};
193    /// let p = ConnectionParams::with(|p| {
194    /// p.host = Some("prod-db.example.com".into());
195    /// p.port = Some(5432);
196    /// p.database = Some("appdb".into());
197    /// p.color = Some(ConnectionColor::Red);
198    /// p.confirm_writes = true;
199    /// p.read_only = true;
200    /// });
201    /// assert_eq!(p.color, Some(ConnectionColor::Red));
202    /// assert!(p.read_only);
203    /// ```
204    #[must_use]
205    pub fn with(f: impl FnOnce(&mut Self)) -> Self {
206        let mut p = Self::default();
207        f(&mut p);
208        p
209    }
210}
211
212/// One pre-connect command.
213///
214/// The `command` string is handed to `sh -c` so users can compose
215/// pipes / redirections without us shipping a parser. Stdout is
216/// captured (trimmed of trailing whitespace) and, when
217/// `save_output_to` is set, stored under that key in the
218/// pre-connect variable map.
219#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
220#[non_exhaustive]
221pub struct PreConnectStep {
222    /// Shell command line. Run via `sh -c`.
223    pub command: String,
224    /// When set, the trimmed stdout of `command` is stored under
225    /// this key in the variable map exposed to the rest of the
226    /// connection params via `${preconnect:NAME}` placeholders.
227    #[serde(default, skip_serializing_if = "Option::is_none")]
228    pub save_output_to: Option<String>,
229    /// Time budget for this step. Defaults to 30 seconds. The whole
230    /// pre-connect sequence is capped at the sum of its steps'
231    /// timeouts so a wedged kubectl call cannot freeze the UI.
232    #[serde(default, skip_serializing_if = "Option::is_none")]
233    pub timeout_secs: Option<u32>,
234    /// When `true`, a non-zero exit aborts the entire connection
235    /// open. When `false`, the failure is logged and the sequence
236    /// continues to the next step. Defaults to `true`.
237    #[serde(default = "default_required")]
238    pub required: bool,
239}
240
241const fn default_required() -> bool {
242    true
243}
244
245impl PreConnectStep {
246    /// Build a step from the bare command line. Convenience for
247    /// tests and any future config-tooling that wants to assemble a
248    /// step without going through serde.
249    #[must_use]
250    pub fn new(command: impl Into<String>) -> Self {
251        Self {
252            command: command.into(),
253            save_output_to: None,
254            timeout_secs: None,
255            required: true,
256        }
257    }
258
259    #[must_use]
260    pub fn with_save_output_to(mut self, key: impl Into<String>) -> Self {
261        self.save_output_to = Some(key.into());
262        self
263    }
264
265    #[must_use]
266    pub const fn with_timeout_secs(mut self, secs: u32) -> Self {
267        self.timeout_secs = Some(secs);
268        self
269    }
270
271    #[must_use]
272    pub const fn with_required(mut self, required: bool) -> Self {
273        self.required = required;
274        self
275    }
276}
277
278/// SSH tunnel parameters. Only the host + user are required; everything
279/// else falls back to the OpenSSH client defaults (`~/.ssh/config`,
280/// the ssh agent, port 22) so a one-line `ssh_host=jump.example.com`
281/// suffices for the common case.
282///
283/// Passwords are deliberately absent: production environments are
284/// expected to authenticate via key files or the ssh-agent, both of
285/// which the underlying `ssh` subprocess picks up for free.
286#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
287#[non_exhaustive]
288pub struct SshConfig {
289    pub host: String,
290    #[serde(default, skip_serializing_if = "Option::is_none")]
291    pub port: Option<u16>,
292    pub user: String,
293    /// Path to the private key. When `None`, the ssh subprocess
294    /// consults `~/.ssh/config` and the agent.
295    #[serde(default, skip_serializing_if = "Option::is_none")]
296    pub key_path: Option<PathBuf>,
297    /// Optional jump host (`-J user@host`). Useful for bastion
298    /// topologies where the actual database host is only reachable
299    /// from inside the bastion's network.
300    #[serde(default, skip_serializing_if = "Option::is_none")]
301    pub jump_host: Option<String>,
302}
303
304impl SshConfig {
305    /// Construct a minimal tunnel spec from the two required fields.
306    /// Tests use this; production code goes through serde.
307    pub fn new(host: impl Into<String>, user: impl Into<String>) -> Self {
308        Self {
309            host: host.into(),
310            port: None,
311            user: user.into(),
312            key_path: None,
313            jump_host: None,
314        }
315    }
316}
317
318/// Standard ANSI transaction isolation levels.
319///
320/// Drivers map this to the engine's native syntax; unsupported levels yield
321/// [`crate::Error::Unsupported`].
322#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
323#[non_exhaustive]
324pub enum IsolationLevel {
325    ReadUncommitted,
326    ReadCommitted,
327    RepeatableRead,
328    Serializable,
329}
330
331/// Open session against a database.
332///
333/// All methods that mutate session state take `&mut self` to make ownership
334/// explicit and to surface accidental concurrent use at compile time.
335///
336/// # Trait shape
337///
338/// This trait uses **native `async fn` in trait** (RPITIT) — every
339/// `async fn` desugars to `-> impl Future + Send`. Because RPITIT is
340/// **not** dyn-compatible, callers that need a trait object should use
341/// [`DynConnection`] instead: it boxes the returned future, costing an
342/// allocation per call but enabling `Box<dyn DynConnection>` /
343/// `Arc<dyn DynConnection>` sites. A blanket
344/// `impl<T: Connection> DynConnection for T` is provided, so any type
345/// that implements `Connection` automatically implements `DynConnection`.
346///
347/// Driver authors implement [`Connection`] directly with `async fn`
348/// bodies — the compiler enforces that every returned future is `Send`.
349pub trait Connection: Send + Sync {
350    /// Execute a single statement and return the materialised result set.
351    ///
352    /// Parameters are bound positionally. Drivers that do not implement
353    /// server-side prepared statements emulate binding by escaping.
354    fn execute(
355        &mut self,
356        sql: &str,
357        params: &[Value],
358    ) -> impl Future<Output = Result<QueryResult>> + Send;
359
360    /// Execute a single statement and return a row stream.
361    ///
362    /// Streams release server-side resources only when the returned
363    /// [`crate::RowStream::close`] is called or the stream is dropped.
364    fn stream(
365        &mut self,
366        sql: &str,
367        params: &[Value],
368    ) -> impl Future<Output = Result<Box<dyn DynRowStream>>> + Send;
369
370    /// Execute a single statement and return a [`QueryStream`] —
371    /// columns up-front, rows arriving asynchronously.
372    ///
373    /// The default implementation builds the stream from
374    /// [`Self::stream`] and captures the column headers eagerly so
375    /// the caller can announce the schema before the first row
376    /// crosses the wire. Drivers that can produce the headers and
377    /// open the cursor in a single round trip can override this for
378    /// a latency win; the trait contract is
379    ///
380    /// 1. `columns()` returns the final column list before the first
381    /// `next_row()` resolves.
382    /// 2. Dropping the [`QueryStream`] releases driver-side cursor /
383    /// portal state.
384    /// 3. [`QueryStream::close`] is awaitable so drivers that must
385    /// flush a server-side close (PG portals, `ClickHouse` HTTP
386    /// bodies) can surface release errors.
387    ///
388    /// `QueryStream` is the canonical entry point for the TUI run
389    /// worker, the MCP query tool's bounded drain, and the v2.0
390    /// export path. Use [`Self::execute`] when you specifically need
391    /// the materialised `QueryResult` with `rows_affected` reporting
392    /// (DDL / DML).
393    fn query(
394        &mut self,
395        sql: &str,
396        params: &[Value],
397    ) -> impl Future<Output = Result<QueryStream>> + Send {
398        async move {
399            let inner = self.stream(sql, params).await?;
400            Ok(QueryStream::new(inner))
401        }
402    }
403
404    /// Begin a transaction with the engine's default isolation level.
405    fn begin(&mut self) -> impl Future<Output = Result<()>> + Send;
406
407    /// Begin a transaction with the requested isolation level.
408    fn begin_with(&mut self, isolation: IsolationLevel) -> impl Future<Output = Result<()>> + Send;
409
410    /// Commit the current transaction.
411    fn commit(&mut self) -> impl Future<Output = Result<()>> + Send;
412
413    /// Roll back the current transaction.
414    fn rollback(&mut self) -> impl Future<Output = Result<()>> + Send;
415
416    /// Establish a savepoint inside the current transaction.
417    ///
418    /// The default implementation reports the feature as unsupported;
419    /// drivers whose [`Capabilities::savepoints`] is `true` override it.
420    fn savepoint(&mut self, name: &str) -> impl Future<Output = Result<()>> + Send {
421        let _ = name;
422        async { Err(crate::Error::unsupported("savepoints")) }
423    }
424
425    /// Release a previously created savepoint.
426    fn release_savepoint(&mut self, name: &str) -> impl Future<Output = Result<()>> + Send {
427        let _ = name;
428        async { Err(crate::Error::unsupported("savepoints")) }
429    }
430
431    /// Roll back to a previously created savepoint without ending the
432    /// surrounding transaction.
433    fn rollback_to_savepoint(&mut self, name: &str) -> impl Future<Output = Result<()>> + Send {
434        let _ = name;
435        async { Err(crate::Error::unsupported("savepoints")) }
436    }
437
438    /// List logical schemas/namespaces visible to the session.
439    fn list_schemas(&mut self) -> impl Future<Output = Result<Vec<Schema>>> + Send;
440
441    /// List tables and views inside `schema`.
442    fn list_tables(&mut self, schema: &str) -> impl Future<Output = Result<Vec<Table>>> + Send;
443
444    /// List every table/view across every visible schema in a single
445    /// round trip when the driver can express it cheaply.
446    ///
447    /// The default implementation falls back to
448    /// [`list_schemas`](Connection::list_schemas) followed by one
449    /// [`list_tables`](Connection::list_tables) per schema, which is
450    /// the historical N+1 path. Drivers that expose a catalogue
451    /// (`information_schema.tables`, `sqlite_master`, `system.tables`)
452    /// override this to issue a single query.
453    ///
454    /// Returned schemas preserve the order produced by `list_schemas`;
455    /// tables inside each schema preserve the order produced by
456    /// `list_tables`.
457    fn list_all_tables(&mut self) -> impl Future<Output = Result<SchemaCatalog>> + Send {
458        async move {
459            let schemas = self.list_schemas().await?;
460            let mut out = Vec::with_capacity(schemas.len());
461            for schema in schemas {
462                let tables = self.list_tables(&schema.name).await?;
463                out.push((schema, tables));
464            }
465            Ok(out)
466        }
467    }
468
469    /// Describe the columns, defaults and constraints of `schema.name`.
470    fn describe_table(
471        &mut self,
472        schema: &str,
473        name: &str,
474    ) -> impl Future<Output = Result<TableSchema>> + Send;
475
476    /// Liveness probe.
477    fn ping(&mut self) -> impl Future<Output = Result<()>> + Send;
478
479    /// Return a cancellation handle that may be used to abort the next query
480    /// dispatched on this connection. `None` means the driver does not
481    /// support out-of-band cancellation.
482    fn cancel_handle(&self) -> Option<Box<dyn DynCancelHandle>>;
483
484    /// Static capability descriptor for this driver.
485    fn capabilities(&self) -> Capabilities;
486
487    /// Fetch the DDL (CREATE statement) for the given table.
488    ///
489    /// The default implementation returns [`crate::Error::Unsupported`];
490    /// drivers override this to return engine-native DDL.
491    fn fetch_ddl(
492        &mut self,
493        _schema: &str,
494        _table: &str,
495    ) -> impl Future<Output = Result<String>> + Send {
496        async { Err(crate::Error::unsupported("fetch_ddl")) }
497    }
498
499    /// Toggle session-level read-only enforcement.
500    ///
501    /// When `true`, the driver instructs the database engine to refuse
502    /// writes for the lifetime of the session (until this method is
503    /// called again with `false`). Mapping per driver:
504    ///
505    /// - `PostgreSQL`: `SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY`
506    /// + `SET default_transaction_read_only TO ON`.
507    /// - `MySQL`/`MariaDB`: `SET SESSION TRANSACTION READ ONLY`.
508    /// - `SQLite`: `PRAGMA query_only = ON`.
509    /// - `ClickHouse`: `SET readonly = 2` (allow SELECT + SET).
510    /// - `DuckDB`: opens are file-mode driven; per-session flip is
511    /// reported as [`crate::Error::Unsupported`] so callers can fall
512    /// back to the connection-string toggle.
513    ///
514    /// The default implementation reports the feature as unsupported so
515    /// driver authors are forced to make an explicit choice (and so a
516    /// security-sensitive caller can detect the absence of enforcement).
517    fn set_read_only(&mut self, read_only: bool) -> impl Future<Output = Result<()>> + Send {
518        let _ = read_only;
519        async { Err(crate::Error::unsupported("set_read_only")) }
520    }
521
522    /// Tear down the underlying connection.
523    fn close(self: Box<Self>) -> impl Future<Output = Result<()>> + Send;
524}
525
526/// Dyn-safe sibling of [`Connection`].
527///
528/// Native `async fn` in trait isn't dyn-compatible — the returned
529/// future has an existential type that can't fit in a vtable slot.
530/// `DynConnection` is the boxing wrapper: every async method returns
531/// `Pin<Box<dyn Future + Send + '_>>`, which **is** vtable-friendly.
532///
533/// A blanket `impl<T: Connection> DynConnection for T` means any
534/// `Connection` automatically satisfies `DynConnection`. Callers that
535/// need a trait object — drivers handed out from a registry, pools
536/// holding heterogeneous connections, the CLI dispatcher — use
537/// `Box<dyn DynConnection>` / `Arc<dyn DynConnection>` and pay the
538/// classic `Box<dyn Future>` alloc per call. Callers with a concrete
539/// type call [`Connection`] directly and avoid the alloc.
540pub trait DynConnection: Send + Sync {
541    fn execute<'a>(
542        &'a mut self,
543        sql: &'a str,
544        params: &'a [Value],
545    ) -> BoxFuture<'a, Result<QueryResult>>;
546
547    fn stream<'a>(
548        &'a mut self,
549        sql: &'a str,
550        params: &'a [Value],
551    ) -> BoxFuture<'a, Result<Box<dyn DynRowStream>>>;
552
553    fn query<'a>(
554        &'a mut self,
555        sql: &'a str,
556        params: &'a [Value],
557    ) -> BoxFuture<'a, Result<QueryStream>>;
558
559    fn begin<'a>(&'a mut self) -> BoxFuture<'a, Result<()>>;
560
561    fn begin_with<'a>(&'a mut self, isolation: IsolationLevel) -> BoxFuture<'a, Result<()>>;
562
563    fn commit<'a>(&'a mut self) -> BoxFuture<'a, Result<()>>;
564
565    fn rollback<'a>(&'a mut self) -> BoxFuture<'a, Result<()>>;
566
567    fn savepoint<'a>(&'a mut self, name: &'a str) -> BoxFuture<'a, Result<()>>;
568
569    fn release_savepoint<'a>(&'a mut self, name: &'a str) -> BoxFuture<'a, Result<()>>;
570
571    fn rollback_to_savepoint<'a>(&'a mut self, name: &'a str) -> BoxFuture<'a, Result<()>>;
572
573    fn list_schemas<'a>(&'a mut self) -> BoxFuture<'a, Result<Vec<Schema>>>;
574
575    fn list_tables<'a>(&'a mut self, schema: &'a str) -> BoxFuture<'a, Result<Vec<Table>>>;
576
577    fn list_all_tables<'a>(&'a mut self) -> BoxFuture<'a, Result<SchemaCatalog>>;
578
579    fn describe_table<'a>(
580        &'a mut self,
581        schema: &'a str,
582        name: &'a str,
583    ) -> BoxFuture<'a, Result<TableSchema>>;
584
585    fn ping<'a>(&'a mut self) -> BoxFuture<'a, Result<()>>;
586
587    fn cancel_handle(&self) -> Option<Box<dyn DynCancelHandle>>;
588
589    fn capabilities(&self) -> Capabilities;
590
591    fn fetch_ddl<'a>(
592        &'a mut self,
593        schema: &'a str,
594        table: &'a str,
595    ) -> BoxFuture<'a, Result<String>>;
596
597    fn set_read_only<'a>(&'a mut self, read_only: bool) -> BoxFuture<'a, Result<()>>;
598
599    fn close(self: Box<Self>) -> BoxFuture<'static, Result<()>>;
600}
601
602impl<T> DynConnection for T
603where
604    T: Connection + 'static,
605{
606    fn execute<'a>(
607        &'a mut self,
608        sql: &'a str,
609        params: &'a [Value],
610    ) -> BoxFuture<'a, Result<QueryResult>> {
611        Box::pin(<Self as Connection>::execute(self, sql, params))
612    }
613
614    fn stream<'a>(
615        &'a mut self,
616        sql: &'a str,
617        params: &'a [Value],
618    ) -> BoxFuture<'a, Result<Box<dyn DynRowStream>>> {
619        Box::pin(<Self as Connection>::stream(self, sql, params))
620    }
621
622    fn query<'a>(
623        &'a mut self,
624        sql: &'a str,
625        params: &'a [Value],
626    ) -> BoxFuture<'a, Result<QueryStream>> {
627        Box::pin(<Self as Connection>::query(self, sql, params))
628    }
629
630    fn begin<'a>(&'a mut self) -> BoxFuture<'a, Result<()>> {
631        Box::pin(<Self as Connection>::begin(self))
632    }
633
634    fn begin_with<'a>(&'a mut self, isolation: IsolationLevel) -> BoxFuture<'a, Result<()>> {
635        Box::pin(<Self as Connection>::begin_with(self, isolation))
636    }
637
638    fn commit<'a>(&'a mut self) -> BoxFuture<'a, Result<()>> {
639        Box::pin(<Self as Connection>::commit(self))
640    }
641
642    fn rollback<'a>(&'a mut self) -> BoxFuture<'a, Result<()>> {
643        Box::pin(<Self as Connection>::rollback(self))
644    }
645
646    fn savepoint<'a>(&'a mut self, name: &'a str) -> BoxFuture<'a, Result<()>> {
647        Box::pin(<Self as Connection>::savepoint(self, name))
648    }
649
650    fn release_savepoint<'a>(&'a mut self, name: &'a str) -> BoxFuture<'a, Result<()>> {
651        Box::pin(<Self as Connection>::release_savepoint(self, name))
652    }
653
654    fn rollback_to_savepoint<'a>(&'a mut self, name: &'a str) -> BoxFuture<'a, Result<()>> {
655        Box::pin(<Self as Connection>::rollback_to_savepoint(self, name))
656    }
657
658    fn list_schemas<'a>(&'a mut self) -> BoxFuture<'a, Result<Vec<Schema>>> {
659        Box::pin(<Self as Connection>::list_schemas(self))
660    }
661
662    fn list_tables<'a>(&'a mut self, schema: &'a str) -> BoxFuture<'a, Result<Vec<Table>>> {
663        Box::pin(<Self as Connection>::list_tables(self, schema))
664    }
665
666    fn list_all_tables<'a>(&'a mut self) -> BoxFuture<'a, Result<SchemaCatalog>> {
667        Box::pin(<Self as Connection>::list_all_tables(self))
668    }
669
670    fn describe_table<'a>(
671        &'a mut self,
672        schema: &'a str,
673        name: &'a str,
674    ) -> BoxFuture<'a, Result<TableSchema>> {
675        Box::pin(<Self as Connection>::describe_table(self, schema, name))
676    }
677
678    fn ping<'a>(&'a mut self) -> BoxFuture<'a, Result<()>> {
679        Box::pin(<Self as Connection>::ping(self))
680    }
681
682    fn cancel_handle(&self) -> Option<Box<dyn DynCancelHandle>> {
683        <Self as Connection>::cancel_handle(self)
684    }
685
686    fn capabilities(&self) -> Capabilities {
687        <Self as Connection>::capabilities(self)
688    }
689
690    fn fetch_ddl<'a>(
691        &'a mut self,
692        schema: &'a str,
693        table: &'a str,
694    ) -> BoxFuture<'a, Result<String>> {
695        Box::pin(<Self as Connection>::fetch_ddl(self, schema, table))
696    }
697
698    fn set_read_only<'a>(&'a mut self, read_only: bool) -> BoxFuture<'a, Result<()>> {
699        Box::pin(<Self as Connection>::set_read_only(self, read_only))
700    }
701
702    fn close(self: Box<Self>) -> BoxFuture<'static, Result<()>> {
703        Box::pin(<Self as Connection>::close(self))
704    }
705}