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}