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 {}