zero_postgres/protocol/backend/
query.rs

1//! Query-related backend messages.
2
3use std::mem::size_of;
4
5use zerocopy::byteorder::big_endian::{I16 as I16BE, I32 as I32BE, U16 as U16BE, U32 as U32BE};
6use zerocopy::{FromBytes, Immutable, KnownLayout};
7
8use crate::error::{Error, Result};
9use crate::protocol::codec::read_cstr;
10use crate::protocol::types::{FormatCode, Oid};
11
12/// Fixed-size tail of a field description (18 bytes).
13#[derive(Debug, Clone, Copy, FromBytes, KnownLayout, Immutable)]
14#[repr(C, packed)]
15pub struct FieldDescriptionTail {
16    /// Table OID (0 if not a table column)
17    pub table_oid: U32BE,
18    /// Column attribute number (0 if not a table column)
19    pub column_id: I16BE,
20    /// Data type OID
21    pub type_oid: U32BE,
22    /// Type size (-1 for variable, -2 for null-terminated)
23    pub type_size: I16BE,
24    /// Type modifier (type-specific)
25    pub type_modifier: I32BE,
26    /// Format code (0=text, 1=binary)
27    pub format: U16BE,
28}
29
30/// Field description within a RowDescription.
31#[derive(Debug, Clone, Copy)]
32pub struct FieldDescription<'a> {
33    /// Field name
34    pub name: &'a str,
35    /// Fixed-size metadata
36    pub tail: &'a FieldDescriptionTail,
37}
38
39impl FieldDescription<'_> {
40    /// Table OID (0 if not a table column)
41    pub fn table_oid(&self) -> Oid {
42        self.tail.table_oid.get()
43    }
44
45    /// Column attribute number (0 if not a table column)
46    pub fn column_id(&self) -> i16 {
47        self.tail.column_id.get()
48    }
49
50    /// Data type OID
51    pub fn type_oid(&self) -> Oid {
52        self.tail.type_oid.get()
53    }
54
55    /// Type size (-1 for variable, -2 for null-terminated)
56    pub fn type_size(&self) -> i16 {
57        self.tail.type_size.get()
58    }
59
60    /// Type modifier (type-specific)
61    pub fn type_modifier(&self) -> i32 {
62        self.tail.type_modifier.get()
63    }
64
65    /// Format code (0=text, 1=binary)
66    pub fn format(&self) -> FormatCode {
67        FormatCode::from_u16(self.tail.format.get())
68    }
69}
70
71/// RowDescription message - describes the columns in a result set.
72#[derive(Debug)]
73pub struct RowDescription<'a> {
74    fields: Vec<FieldDescription<'a>>,
75}
76
77impl<'a> RowDescription<'a> {
78    /// Parse a RowDescription message from payload bytes.
79    pub fn parse(payload: &'a [u8]) -> Result<Self> {
80        let num_fields = U16BE::ref_from_bytes(&payload[..2])
81            .map_err(|e| Error::Protocol(format!("RowDescription header: {e:?}")))?
82            .get() as usize;
83        let mut fields = Vec::with_capacity(num_fields);
84        let mut data = &payload[2..];
85
86        const TAIL_SIZE: usize = size_of::<FieldDescriptionTail>();
87
88        for _ in 0..num_fields {
89            let (name, rest) = read_cstr(data)?;
90            let tail = FieldDescriptionTail::ref_from_bytes(&rest[..TAIL_SIZE])
91                .map_err(|e| Error::Protocol(format!("FieldDescription tail: {e:?}")))?;
92
93            fields.push(FieldDescription { name, tail });
94
95            data = &rest[TAIL_SIZE..];
96        }
97
98        Ok(Self { fields })
99    }
100
101    /// Get the number of fields.
102    pub fn len(&self) -> usize {
103        self.fields.len()
104    }
105
106    /// Check if there are no fields.
107    pub fn is_empty(&self) -> bool {
108        self.fields.is_empty()
109    }
110
111    /// Get field descriptions.
112    pub fn fields(&self) -> &[FieldDescription<'a>] {
113        &self.fields
114    }
115
116    /// Iterate over field descriptions.
117    pub fn iter(&self) -> impl Iterator<Item = &FieldDescription<'a>> {
118        self.fields.iter()
119    }
120}
121
122/// DataRow message header.
123#[derive(Debug, Clone, Copy, FromBytes, KnownLayout, Immutable)]
124#[repr(C, packed)]
125pub struct DataRowHead {
126    /// Number of columns
127    pub num_columns: U16BE,
128}
129
130/// DataRow message - contains a single row of data.
131#[derive(Debug, Clone, Copy)]
132pub struct DataRow<'a> {
133    /// Number of columns
134    num_columns: u16,
135    /// Column data (after the column count)
136    columns_data: &'a [u8],
137}
138
139impl<'a> DataRow<'a> {
140    /// Parse a DataRow message from payload bytes.
141    pub fn parse(payload: &'a [u8]) -> Result<Self> {
142        let head = DataRowHead::ref_from_bytes(&payload[..2])
143            .map_err(|e| Error::Protocol(format!("DataRow header: {e:?}")))?;
144
145        Ok(Self {
146            num_columns: head.num_columns.get(),
147            columns_data: &payload[2..],
148        })
149    }
150
151    /// Get the number of columns.
152    pub fn len(&self) -> usize {
153        self.num_columns as usize
154    }
155
156    /// Check if there are no columns.
157    pub fn is_empty(&self) -> bool {
158        self.num_columns == 0
159    }
160
161    /// Create an iterator over column values.
162    ///
163    /// Each item is `Option<&[u8]>` where `None` represents NULL.
164    pub fn iter(&self) -> DataRowIter<'a> {
165        DataRowIter {
166            remaining: self.columns_data,
167        }
168    }
169
170    /// Get a column value by index.
171    ///
172    /// Returns `None` if the column is NULL, `Some(bytes)` otherwise.
173    pub fn get(&self, index: usize) -> Option<Option<&'a [u8]>> {
174        self.iter().nth(index)
175    }
176}
177
178/// Iterator over column values in a DataRow.
179#[derive(Debug, Clone)]
180pub struct DataRowIter<'a> {
181    remaining: &'a [u8],
182}
183
184impl<'a> Iterator for DataRowIter<'a> {
185    type Item = Option<&'a [u8]>;
186
187    fn next(&mut self) -> Option<Self::Item> {
188        let len;
189        (len, self.remaining) = self.remaining.split_at_checked(4)?;
190        let len = i32::from_be_bytes([len[0], len[1], len[2], len[3]]);
191
192        if len == -1 {
193            // NULL value
194            Some(None)
195        } else {
196            let len = len as usize;
197            if self.remaining.len() < len {
198                return None;
199            }
200
201            let value;
202            (value, self.remaining) = self.remaining.split_at_checked(len)?;
203            Some(Some(value))
204        }
205    }
206}
207
208/// CommandComplete message - indicates successful completion of a command.
209#[derive(Debug, Clone, Copy)]
210pub struct CommandComplete<'a> {
211    /// Command tag (e.g., "SELECT 5", "INSERT 0 1", "UPDATE 10")
212    pub tag: &'a str,
213}
214
215impl<'a> CommandComplete<'a> {
216    /// Parse a CommandComplete message from payload bytes.
217    pub fn parse(payload: &'a [u8]) -> Result<Self> {
218        let (tag, _) = read_cstr(payload)?;
219        Ok(Self { tag })
220    }
221
222    /// Parse the number of rows affected from the command tag.
223    ///
224    /// Returns `Some(count)` for commands like SELECT, INSERT, UPDATE, DELETE.
225    /// Returns `None` for other commands or parse failures.
226    pub fn rows_affected(&self) -> Option<u64> {
227        // Command tags are like:
228        // - "SELECT 5"
229        // - "INSERT 0 1" (oid, rows)
230        // - "UPDATE 10"
231        // - "DELETE 3"
232        // - "COPY 5"
233        let parts: Vec<&str> = self.tag.split_whitespace().collect();
234
235        match parts.as_slice() {
236            ["SELECT", count] => count.parse().ok(),
237            ["INSERT", _oid, count] => count.parse().ok(),
238            ["UPDATE", count] => count.parse().ok(),
239            ["DELETE", count] => count.parse().ok(),
240            ["COPY", count] => count.parse().ok(),
241            ["MOVE", count] => count.parse().ok(),
242            ["FETCH", count] => count.parse().ok(),
243            _ => None,
244        }
245    }
246
247    /// Get the command name from the tag.
248    pub fn command(&self) -> Option<&str> {
249        self.tag.split_whitespace().next()
250    }
251}
252
253/// EmptyQueryResponse message - response to an empty query string.
254#[derive(Debug, Clone, Copy)]
255pub struct EmptyQueryResponse;
256
257impl EmptyQueryResponse {
258    /// Parse an EmptyQueryResponse message from payload bytes.
259    pub fn parse(_payload: &[u8]) -> Result<Self> {
260        Ok(Self)
261    }
262}