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