Skip to main content

qail_pg/protocol/wire/
types.rs

1//! PostgreSQL wire protocol types — message enums, structs, and error types.
2//!
3//! Reference: <https://www.postgresql.org/docs/current/protocol-message-formats.html>
4
5/// PostgreSQL protocol 3.0 (legacy baseline).
6pub const PROTOCOL_VERSION_3_0: i32 = 196608;
7/// PostgreSQL protocol 3.2 (new default request).
8pub const PROTOCOL_VERSION_3_2: i32 = 196610;
9
10/// Frontend (client → server) message types
11#[derive(Debug, Clone)]
12pub enum FrontendMessage {
13    /// Startup message (sent first, no type byte)
14    Startup {
15        /// Database role / user name.
16        user: String,
17        /// Target database name.
18        database: String,
19        /// Startup protocol version (for example `196610` for protocol 3.2).
20        protocol_version: i32,
21        /// Additional startup parameters (e.g. `replication=database`).
22        startup_params: Vec<(String, String)>,
23    },
24    /// Password response (MD5 or cleartext).
25    PasswordMessage(String),
26    /// Simple query (SQL text).
27    Query(String),
28    /// Parse (prepared statement)
29    Parse {
30        /// Prepared statement name (empty string = unnamed).
31        name: String,
32        /// SQL query text with `$1`-style parameter placeholders.
33        query: String,
34        /// OIDs of the parameter types (empty = server infers).
35        param_types: Vec<u32>,
36    },
37    /// Bind parameters to prepared statement
38    Bind {
39        /// Destination portal name (empty = unnamed).
40        portal: String,
41        /// Source prepared statement name.
42        statement: String,
43        /// Parameter values (`None` = SQL NULL).
44        params: Vec<Option<Vec<u8>>>,
45    },
46    /// Execute portal
47    Execute {
48        /// Portal name to execute.
49        portal: String,
50        /// Maximum rows to return (0 = no limit).
51        max_rows: i32,
52    },
53    /// Sync — marks the end of an extended-query pipeline.
54    Sync,
55    /// Terminate — closes the connection.
56    Terminate,
57    /// SASL initial response (first message in SCRAM)
58    SASLInitialResponse {
59        /// SASL mechanism name (e.g. `SCRAM-SHA-256`).
60        mechanism: String,
61        /// Client-first message bytes.
62        data: Vec<u8>,
63    },
64    /// SASL response (subsequent messages in SCRAM)
65    SASLResponse(Vec<u8>),
66    /// GSS/SSPI response token.
67    GSSResponse(Vec<u8>),
68    /// CopyFail — abort a COPY IN with an error message
69    CopyFail(String),
70    /// Close — explicitly release a prepared statement or portal
71    Close {
72        /// `true` for portal, `false` for prepared statement.
73        is_portal: bool,
74        /// Name of the portal or statement to close.
75        name: String,
76    },
77}
78
79/// Backend (server → client) message types
80#[derive(Debug, Clone)]
81pub enum BackendMessage {
82    /// Authentication request
83    /// Authentication succeeded.
84    AuthenticationOk,
85    /// Server requests cleartext password.
86    AuthenticationCleartextPassword,
87    /// Server requests MD5-hashed password; salt provided.
88    AuthenticationMD5Password([u8; 4]),
89    /// Server requests Kerberos V5 authentication.
90    AuthenticationKerberosV5,
91    /// Server requests GSSAPI authentication.
92    AuthenticationGSS,
93    /// Server requests SCM credential auth (Unix-domain peer credential exchange).
94    ///
95    /// PostgreSQL auth code `6`. This is typically used with local Unix sockets
96    /// and requires ancillary credential passing at the socket layer.
97    AuthenticationSCMCredential,
98    /// Server sends GSSAPI/SSPI continuation token.
99    AuthenticationGSSContinue(Vec<u8>),
100    /// Server requests SSPI authentication.
101    AuthenticationSSPI,
102    /// Server initiates SASL handshake with supported mechanisms.
103    AuthenticationSASL(Vec<String>),
104    /// SASL challenge from server.
105    AuthenticationSASLContinue(Vec<u8>),
106    /// SASL authentication complete; final server data.
107    AuthenticationSASLFinal(Vec<u8>),
108    /// Parameter status (server config)
109    ParameterStatus {
110        /// Parameter name (e.g. `server_version`, `TimeZone`).
111        name: String,
112        /// Current parameter value.
113        value: String,
114    },
115    /// Backend key data (for cancel)
116    BackendKeyData {
117        /// Backend process ID (used for cancel requests).
118        process_id: i32,
119        /// Cancel secret key bytes (`4..=256` bytes).
120        secret_key: Vec<u8>,
121    },
122    /// Protocol minor-version negotiation (message type `'v'`).
123    ///
124    /// Sent by servers that need to negotiate down from the requested startup
125    /// protocol minor version while continuing the startup sequence.
126    NegotiateProtocolVersion {
127        /// Highest protocol minor version this server supports.
128        newest_minor_supported: i32,
129        /// Startup options not recognized by the server.
130        unrecognized_protocol_options: Vec<String>,
131    },
132    /// Server is ready; transaction state indicated.
133    ReadyForQuery(TransactionStatus),
134    /// Column metadata for the upcoming data rows.
135    RowDescription(Vec<FieldDescription>),
136    /// One data row; each element is `None` for SQL NULL or the raw bytes.
137    DataRow(Vec<Option<Vec<u8>>>),
138    /// Command completed with a tag like `SELECT 5` or `INSERT 0 1`.
139    CommandComplete(String),
140    /// Error response with structured fields (severity, code, message, etc.).
141    ErrorResponse(ErrorFields),
142    /// Parse step succeeded.
143    ParseComplete,
144    /// Bind step succeeded.
145    BindComplete,
146    /// Describe returned no row description (e.g. for DML statements).
147    NoData,
148    /// Execute reached row limit (`max_rows`) and suspended the portal.
149    PortalSuspended,
150    /// Copy in response (server ready to receive COPY data)
151    CopyInResponse {
152        /// Overall format: 0 = text, 1 = binary.
153        format: u8,
154        /// Per-column format codes.
155        column_formats: Vec<u8>,
156    },
157    /// Copy out response (server will send COPY data)
158    CopyOutResponse {
159        /// Overall format: 0 = text, 1 = binary.
160        format: u8,
161        /// Per-column format codes.
162        column_formats: Vec<u8>,
163    },
164    /// Copy both response (used by streaming replication).
165    CopyBothResponse {
166        /// Overall format: 0 = text, 1 = binary.
167        format: u8,
168        /// Per-column format codes.
169        column_formats: Vec<u8>,
170    },
171    /// Raw COPY data chunk from the server.
172    CopyData(Vec<u8>),
173    /// COPY transfer complete.
174    CopyDone,
175    /// Notification response (async notification from LISTEN/NOTIFY)
176    NotificationResponse {
177        /// Backend process ID that sent the notification.
178        process_id: i32,
179        /// Channel name.
180        channel: String,
181        /// Notification payload string.
182        payload: String,
183    },
184    /// Empty query string was submitted.
185    EmptyQueryResponse,
186    /// Notice response (warning/info messages, not errors)
187    NoticeResponse(ErrorFields),
188    /// Parameter description (OIDs of parameters in a prepared statement)
189    /// Sent by server in response to Describe(Statement)
190    ParameterDescription(Vec<u32>),
191    /// Close complete (server confirmation that a prepared statement/portal was released)
192    CloseComplete,
193}
194
195/// Transaction status
196#[derive(Debug, Clone, Copy)]
197pub enum TransactionStatus {
198    /// Not inside a transaction block (`I`).
199    Idle,
200    /// Inside a transaction block (`T`).
201    InBlock,
202    /// Inside a failed transaction block (`E`).
203    Failed,
204}
205
206/// Field description in RowDescription
207#[derive(Debug, Clone)]
208pub struct FieldDescription {
209    /// Column name (or alias).
210    pub name: String,
211    /// OID of the source table (0 if not a table column).
212    pub table_oid: u32,
213    /// Column attribute number within the table (0 if not a table column).
214    pub column_attr: i16,
215    /// OID of the column's data type.
216    pub type_oid: u32,
217    /// Data type size in bytes (negative = variable-length).
218    pub type_size: i16,
219    /// Type-specific modifier (e.g. precision for `numeric`).
220    pub type_modifier: i32,
221    /// Format code: 0 = text, 1 = binary.
222    pub format: i16,
223}
224
225/// Error fields from ErrorResponse
226#[derive(Debug, Clone, Default)]
227pub struct ErrorFields {
228    /// Severity level (e.g. `ERROR`, `FATAL`, `WARNING`).
229    pub severity: String,
230    /// SQLSTATE error code (e.g. `23505` for unique violation).
231    pub code: String,
232    /// Human-readable error message.
233    pub message: String,
234    /// Optional detailed error description.
235    pub detail: Option<String>,
236    /// Optional hint for resolving the error.
237    pub hint: Option<String>,
238}
239
240#[derive(Debug, Clone, PartialEq, Eq)]
241pub enum FrontendEncodeError {
242    InteriorNul(&'static str),
243    MessageTooLarge(usize),
244    TooManyParams(usize),
245    InvalidMaxRows(i32),
246    InvalidStartupParam(String),
247}
248
249impl std::fmt::Display for FrontendEncodeError {
250    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251        match self {
252            Self::InteriorNul(field) => write!(f, "field `{}` contains interior NUL byte", field),
253            Self::MessageTooLarge(len) => write!(f, "message too large for wire length: {}", len),
254            Self::TooManyParams(n) => write!(f, "too many params for i16 wire count: {}", n),
255            Self::InvalidMaxRows(v) => write!(f, "invalid Execute max_rows (must be >= 0): {}", v),
256            Self::InvalidStartupParam(msg) => write!(f, "invalid startup parameter: {}", msg),
257        }
258    }
259}
260
261impl std::error::Error for FrontendEncodeError {}