Skip to main content

ferrule_sql/
error.rs

1use thiserror::Error;
2
3/// Errors originating in the `ferrule-sql` driver and write-path layer.
4///
5/// Every backend method, URL parse, connect dispatch, transaction
6/// helper, and copy routine returns this type. Variant names and tuple
7/// shapes are load-bearing across the workspace (the CLI pattern-matches
8/// [`SqlError::QueryFailed`] in several hot paths), so preserve them when
9/// editing.
10///
11/// `RegistryError` is registry/CLI-level rather than driver-level; it
12/// rides along here as a deliberate minimal-diff choice during the
13/// `ferrule-core` -> `ferrule-sql` extraction and is a candidate for a
14/// later relocation to a core-side error type.
15#[derive(Error, Debug)]
16pub enum SqlError {
17    #[error("unsupported URL scheme: {0}")]
18    UnsupportedScheme(String),
19
20    #[error("invalid connection URL: {0}")]
21    InvalidUrl(String),
22
23    #[error("backend '{0}' is not enabled — recompile with the appropriate feature")]
24    BackendDisabled(String),
25
26    #[error("connection failed: {0}")]
27    ConnectionFailed(String),
28
29    #[error("query failed: {0}")]
30    QueryFailed(String),
31
32    /// Backend-native bulk-load path is not usable at runtime (server
33    /// config, missing capability, permission denied, target relation
34    /// is not a base table, etc.). Callers may retry on the generic
35    /// INSERT path. The string is intended for stderr only; do not
36    /// match on it.
37    #[error("bulk path unavailable: {0}")]
38    BulkUnavailable(String),
39
40    #[error("TLS error: {0}")]
41    TlsError(String),
42
43    #[error("I/O error: {0}")]
44    Io(#[from] std::io::Error),
45
46    /// Host key mismatch during SSH tunnel setup (always fatal).
47    #[cfg(feature = "ssh")]
48    #[error("SSH host key mismatch for {host}:{port}")]
49    SshHostKeyMismatch { host: String, port: u16 },
50
51    /// Unknown host during SSH tunnel setup (can be TOFU-prompted
52    /// interactively by the CLI layer).
53    #[cfg(feature = "ssh")]
54    #[error("SSH unknown host {host}:{port}")]
55    SshUnknownHost {
56        host: String,
57        port: u16,
58        algorithm: String,
59        fingerprint: String,
60        /// Boxed so this (otherwise large) variant doesn't inflate the
61        /// whole `SqlError` enum — every synchronous backend method
62        /// returns `Result<_, SqlError>` by value, so an oversized error
63        /// variant would bloat every `Ok` path too.
64        key: Box<russh::keys::ssh_key::PublicKey>,
65    },
66
67    #[error("timeout")]
68    Timeout,
69
70    /// A single cell exceeded the configured `max_cell_bytes` guard.
71    ///
72    /// Raised by the read paths (streaming cursor and eager `query`)
73    /// **before** the offending value is retained, so the guard caps
74    /// peak memory at one over-budget cell rather than letting a
75    /// pathological `bytea` / `TEXT` blow the heap. `row` is the
76    /// 0-based row ordinal within the current result; `column` names the
77    /// offending column; `size` and `cap` are byte counts.
78    #[error(
79        "cell too large: row {row}, column {column:?} is {size} bytes, \
80         exceeds the {cap}-byte cap (raise SizeGuards::max_cell_bytes, or \
81         stream the column out via --format csv/jsonl)"
82    )]
83    CellTooLarge {
84        row: u64,
85        column: String,
86        size: usize,
87        cap: usize,
88    },
89
90    /// A whole row's measured byte size exceeded `max_row_bytes`.
91    ///
92    /// Like [`CellTooLarge`](Self::CellTooLarge) this fires before the
93    /// row is appended to any buffer, so an unexpectedly wide row fails
94    /// fast instead of being materialized. `row` is the 0-based ordinal;
95    /// `size`/`cap` are byte counts.
96    #[error(
97        "row too large: row {row} is {size} bytes, exceeds the \
98         {cap}-byte cap (raise SizeGuards::max_row_bytes, or stream via \
99         --format csv/jsonl)"
100    )]
101    RowTooLarge { row: u64, size: usize, cap: usize },
102
103    /// The running total of buffered row bytes crossed
104    /// `max_total_buffered_bytes` while materializing an eager result.
105    ///
106    /// This is the guard the CLI's eager table path relies on: rather
107    /// than collect an unbounded `Vec<Row>` for a huge table, the eager
108    /// `query` accumulates a byte tally and aborts once it crosses the
109    /// cap. `rows_buffered` is how many rows had been accumulated; `cap`
110    /// is the byte ceiling.
111    #[error(
112        "result too large: buffered {rows_buffered} rows exceeding the \
113         {cap}-byte total cap (raise SizeGuards::max_total_buffered_bytes, \
114         or stream the result via the cursor API / --format csv/jsonl)"
115    )]
116    BufferTooLarge { rows_buffered: u64, cap: usize },
117
118    #[error("registry error: {0}")]
119    RegistryError(String),
120}