Skip to main content

uqa_pg_wire/
backend.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7use crate::codec::{i16_len, i32_len, Writer};
8use crate::protocol::{CancelKey, FormatCode, PgWireError, ProtocolVersion, TransactionStatus};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum Authentication {
12    Ok,
13    KerberosV5,
14    CleartextPassword,
15    Md5Password([u8; 4]),
16    Gss,
17    GssContinue(Vec<u8>),
18    Sspi,
19    Sasl { mechanisms: Vec<String> },
20    SaslContinue(Vec<u8>),
21    SaslFinal(Vec<u8>),
22}
23
24impl Authentication {
25    pub(crate) const fn description(&self) -> &'static str {
26        match self {
27            Self::Ok => "AuthenticationOk",
28            Self::KerberosV5 => "AuthenticationKerberosV5",
29            Self::CleartextPassword => "AuthenticationCleartextPassword",
30            Self::Md5Password(_) => "AuthenticationMD5Password",
31            Self::Gss => "AuthenticationGSS",
32            Self::GssContinue(_) => "AuthenticationGSSContinue",
33            Self::Sspi => "AuthenticationSSPI",
34            Self::Sasl { .. } => "AuthenticationSASL",
35            Self::SaslContinue(_) => "AuthenticationSASLContinue",
36            Self::SaslFinal(_) => "AuthenticationSASLFinal",
37        }
38    }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum SSLResponse {
43    Accept,
44    Reject,
45}
46
47impl SSLResponse {
48    pub const fn encode(self) -> [u8; 1] {
49        match self {
50            Self::Accept => *b"S",
51            Self::Reject => *b"N",
52        }
53    }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum GSSEncResponse {
58    Accept,
59    Reject,
60}
61
62impl GSSEncResponse {
63    pub const fn encode(self) -> [u8; 1] {
64        match self {
65            Self::Accept => *b"G",
66            Self::Reject => *b"N",
67        }
68    }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct BackendKeyData {
73    pub process_id: i32,
74    pub secret_key: CancelKey,
75}
76
77impl BackendKeyData {
78    #[must_use]
79    pub fn legacy(process_id: i32, secret_key: i32) -> Self {
80        Self {
81            process_id,
82            secret_key: CancelKey::from_i32(secret_key),
83        }
84    }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub enum BackendMessage {
89    Authentication(Authentication),
90    BackendKeyData(BackendKeyData),
91    NegotiateProtocolVersion {
92        newest_protocol_version: ProtocolVersion,
93        unrecognized_options: Vec<String>,
94    },
95    ParameterStatus {
96        name: String,
97        value: String,
98    },
99    ReadyForQuery(TransactionStatus),
100    RowDescription(Vec<FieldDescription>),
101    DataRow(Vec<Option<Vec<u8>>>),
102    CommandComplete(String),
103    EmptyQueryResponse,
104    ErrorResponse(ErrorOrNotice),
105    NoticeResponse(ErrorOrNotice),
106    ParseComplete,
107    BindComplete,
108    CloseComplete,
109    NoData,
110    ParameterDescription(Vec<u32>),
111    PortalSuspended,
112    CopyInResponse(CopyResponse),
113    CopyOutResponse(CopyResponse),
114    CopyBothResponse(CopyResponse),
115    CopyData(Vec<u8>),
116    CopyDone,
117    FunctionCallResponse(Option<Vec<u8>>),
118    NotificationResponse(NotificationResponse),
119}
120
121#[derive(Debug, Clone, PartialEq, Eq)]
122pub struct FieldDescription {
123    pub name: String,
124    pub table_oid: u32,
125    pub column_attribute_number: i16,
126    pub type_oid: u32,
127    pub type_size: i16,
128    pub type_modifier: i32,
129    pub format: FormatCode,
130}
131
132impl FieldDescription {
133    pub fn text(name: impl Into<String>, type_oid: u32, type_size: i16) -> Self {
134        Self {
135            name: name.into(),
136            table_oid: 0,
137            column_attribute_number: 0,
138            type_oid,
139            type_size,
140            type_modifier: -1,
141            format: FormatCode::Text,
142        }
143    }
144}
145
146#[derive(Debug, Clone, PartialEq, Eq)]
147pub struct CopyResponse {
148    pub overall_format: FormatCode,
149    pub column_formats: Vec<FormatCode>,
150}
151
152#[derive(Debug, Clone, PartialEq, Eq)]
153pub struct NotificationResponse {
154    pub process_id: i32,
155    pub channel: String,
156    pub payload: String,
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160pub enum NoticeSeverity {
161    Error,
162    Fatal,
163    Panic,
164    Warning,
165    Notice,
166    Debug,
167    Info,
168    Log,
169}
170
171impl NoticeSeverity {
172    const fn as_str(self) -> &'static str {
173        match self {
174            Self::Error => "ERROR",
175            Self::Fatal => "FATAL",
176            Self::Panic => "PANIC",
177            Self::Warning => "WARNING",
178            Self::Notice => "NOTICE",
179            Self::Debug => "DEBUG",
180            Self::Info => "INFO",
181            Self::Log => "LOG",
182        }
183    }
184}
185
186#[derive(Debug, Clone, PartialEq, Eq)]
187pub struct ErrorOrNotice {
188    pub severity: NoticeSeverity,
189    pub code: String,
190    pub message: String,
191    pub detail: Option<String>,
192    pub hint: Option<String>,
193    pub position: Option<i32>,
194    pub where_: Option<String>,
195    pub schema: Option<String>,
196    pub table: Option<String>,
197    pub column: Option<String>,
198    pub data_type: Option<String>,
199    pub constraint: Option<String>,
200    pub file: Option<String>,
201    pub line: Option<i32>,
202    pub routine: Option<String>,
203}
204
205impl ErrorOrNotice {
206    pub fn error(code: impl Into<String>, message: impl Into<String>) -> Self {
207        Self {
208            severity: NoticeSeverity::Error,
209            code: code.into(),
210            message: message.into(),
211            detail: None,
212            hint: None,
213            position: None,
214            where_: None,
215            schema: None,
216            table: None,
217            column: None,
218            data_type: None,
219            constraint: None,
220            file: None,
221            line: None,
222            routine: None,
223        }
224    }
225}
226
227impl BackendMessage {
228    pub fn encode(&self) -> Result<Vec<u8>, PgWireError> {
229        self.encode_for_protocol(ProtocolVersion::LATEST)
230    }
231
232    pub fn encode_for_protocol(
233        &self,
234        protocol_version: ProtocolVersion,
235    ) -> Result<Vec<u8>, PgWireError> {
236        match self {
237            Self::Authentication(auth) => encode_authentication(auth),
238            Self::BackendKeyData(data) => encode_backend_key_data(data, protocol_version),
239            Self::NegotiateProtocolVersion {
240                newest_protocol_version,
241                unrecognized_options,
242            } => encode_negotiate_protocol_version(*newest_protocol_version, unrecognized_options),
243            Self::ParameterStatus { name, value } => encode_parameter_status(name, value),
244            Self::ReadyForQuery(status) => encode_ready_for_query(*status),
245            Self::RowDescription(fields) => encode_row_description(fields),
246            Self::DataRow(values) => encode_data_row(values),
247            Self::CommandComplete(tag) => encode_command_complete(tag),
248            Self::EmptyQueryResponse => encode_empty_body(b'I'),
249            Self::ErrorResponse(error) => encode_error_or_notice(b'E', error),
250            Self::NoticeResponse(notice) => encode_error_or_notice(b'N', notice),
251            Self::ParseComplete => encode_empty_body(b'1'),
252            Self::BindComplete => encode_empty_body(b'2'),
253            Self::CloseComplete => encode_empty_body(b'3'),
254            Self::NoData => encode_empty_body(b'n'),
255            Self::ParameterDescription(oids) => encode_parameter_description(oids),
256            Self::PortalSuspended => encode_empty_body(b's'),
257            Self::CopyInResponse(response) => encode_copy_response(b'G', response),
258            Self::CopyOutResponse(response) => encode_copy_response(b'H', response),
259            Self::CopyBothResponse(response) => encode_copy_response(b'W', response),
260            Self::CopyData(bytes) => Writer::frame(b'd', bytes),
261            Self::CopyDone => encode_empty_body(b'c'),
262            Self::FunctionCallResponse(value) => encode_function_call_response(value.as_deref()),
263            Self::NotificationResponse(notification) => encode_notification_response(notification),
264        }
265    }
266}
267
268pub fn encode_all(messages: &[BackendMessage]) -> Result<Vec<u8>, PgWireError> {
269    encode_all_for_protocol(messages, ProtocolVersion::LATEST)
270}
271
272pub fn encode_all_for_protocol(
273    messages: &[BackendMessage],
274    protocol_version: ProtocolVersion,
275) -> Result<Vec<u8>, PgWireError> {
276    let mut out = Vec::new();
277    for message in messages {
278        out.extend(message.encode_for_protocol(protocol_version)?);
279    }
280    Ok(out)
281}
282
283pub const fn encode_ssl_response(response: SSLResponse) -> [u8; 1] {
284    response.encode()
285}
286
287pub const fn encode_gssenc_response(response: GSSEncResponse) -> [u8; 1] {
288    response.encode()
289}
290
291pub const TYPE_BOOL: u32 = 16;
292pub const TYPE_BYTEA: u32 = 17;
293pub const TYPE_INT8: u32 = 20;
294pub const TYPE_INT2: u32 = 21;
295pub const TYPE_INT4: u32 = 23;
296pub const TYPE_TEXT: u32 = 25;
297pub const TYPE_FLOAT4: u32 = 700;
298pub const TYPE_FLOAT8: u32 = 701;
299pub const TYPE_VARCHAR: u32 = 1_043;
300pub const TYPE_DATE: u32 = 1_082;
301pub const TYPE_TIMESTAMP: u32 = 1_114;
302pub const TYPE_TIMESTAMPTZ: u32 = 1_184;
303pub const TYPE_JSON: u32 = 114;
304pub const TYPE_JSONB: u32 = 3_802;
305
306pub mod sqlstate {
307    pub const SUCCESSFUL_COMPLETION: &str = "00000";
308    pub const WARNING: &str = "01000";
309    pub const PROTOCOL_VIOLATION: &str = "08P01";
310    pub const FEATURE_NOT_SUPPORTED: &str = "0A000";
311    pub const INVALID_PARAMETER_VALUE: &str = "22023";
312    pub const QUERY_CANCELED: &str = "57014";
313    pub const SYNTAX_ERROR: &str = "42601";
314    pub const UNDEFINED_TABLE: &str = "42P01";
315    pub const INTERNAL_ERROR: &str = "XX000";
316}
317
318fn encode_authentication(auth: &Authentication) -> Result<Vec<u8>, PgWireError> {
319    let mut body = Writer::new();
320    match auth {
321        Authentication::Ok => body.write_i32(0),
322        Authentication::KerberosV5 => body.write_i32(2),
323        Authentication::CleartextPassword => body.write_i32(3),
324        Authentication::Md5Password(salt) => {
325            body.write_i32(5);
326            body.write_bytes(salt);
327        }
328        Authentication::Gss => body.write_i32(7),
329        Authentication::GssContinue(data) => {
330            body.write_i32(8);
331            body.write_bytes(data);
332        }
333        Authentication::Sspi => body.write_i32(9),
334        Authentication::Sasl { mechanisms } => {
335            if mechanisms.is_empty() {
336                return Err(PgWireError::EmptySaslMechanismList);
337            }
338            body.write_i32(10);
339            for mechanism in mechanisms {
340                if mechanism.is_empty() {
341                    return Err(PgWireError::EmptySaslMechanism);
342                }
343                body.write_cstring(mechanism, "SASL mechanism")?;
344            }
345            body.write_byte(0);
346        }
347        Authentication::SaslContinue(data) => {
348            body.write_i32(11);
349            body.write_bytes(data);
350        }
351        Authentication::SaslFinal(data) => {
352            body.write_i32(12);
353            body.write_bytes(data);
354        }
355    }
356    Writer::frame(b'R', &body.into_inner())
357}
358
359fn encode_backend_key_data(
360    data: &BackendKeyData,
361    protocol_version: ProtocolVersion,
362) -> Result<Vec<u8>, PgWireError> {
363    data.secret_key
364        .validate_for_backend_key_data(protocol_version)?;
365    let mut body = Writer::new();
366    body.write_i32(data.process_id);
367    body.write_bytes(data.secret_key.as_bytes());
368    Writer::frame(b'K', &body.into_inner())
369}
370
371fn encode_negotiate_protocol_version(
372    newest_protocol_version: ProtocolVersion,
373    unrecognized_options: &[String],
374) -> Result<Vec<u8>, PgWireError> {
375    if newest_protocol_version.negotiate()? != newest_protocol_version {
376        return Err(PgWireError::UnsupportedProtocolVersion(
377            newest_protocol_version.raw(),
378        ));
379    }
380    let mut body = Writer::new();
381    body.write_i32(newest_protocol_version.raw());
382    body.write_i32(i32_len(
383        unrecognized_options.len(),
384        "NegotiateProtocolVersion option count",
385    )?);
386    for option in unrecognized_options {
387        body.write_cstring(option, "NegotiateProtocolVersion option")?;
388    }
389    Writer::frame(b'v', &body.into_inner())
390}
391
392fn encode_parameter_status(name: &str, value: &str) -> Result<Vec<u8>, PgWireError> {
393    let mut body = Writer::new();
394    body.write_cstring(name, "ParameterStatus name")?;
395    body.write_cstring(value, "ParameterStatus value")?;
396    Writer::frame(b'S', &body.into_inner())
397}
398
399fn encode_ready_for_query(status: TransactionStatus) -> Result<Vec<u8>, PgWireError> {
400    Writer::frame(b'Z', &[status.as_byte()])
401}
402
403fn encode_row_description(fields: &[FieldDescription]) -> Result<Vec<u8>, PgWireError> {
404    let mut body = Writer::new();
405    body.write_i16(i16_len(fields.len(), "RowDescription field")?);
406    for field in fields {
407        body.write_cstring(&field.name, "RowDescription field name")?;
408        body.write_u32(field.table_oid);
409        body.write_i16(field.column_attribute_number);
410        body.write_u32(field.type_oid);
411        body.write_i16(field.type_size);
412        body.write_i32(field.type_modifier);
413        body.write_format(field.format);
414    }
415    Writer::frame(b'T', &body.into_inner())
416}
417
418fn encode_data_row(values: &[Option<Vec<u8>>]) -> Result<Vec<u8>, PgWireError> {
419    let mut body = Writer::new();
420    body.write_i16(i16_len(values.len(), "DataRow column")?);
421    for value in values {
422        match value {
423            Some(bytes) => {
424                body.write_i32(i32_len(bytes.len(), "DataRow value")?);
425                body.write_bytes(bytes);
426            }
427            None => body.write_i32(-1),
428        }
429    }
430    Writer::frame(b'D', &body.into_inner())
431}
432
433fn encode_command_complete(tag: &str) -> Result<Vec<u8>, PgWireError> {
434    let mut body = Writer::new();
435    body.write_cstring(tag, "CommandComplete tag")?;
436    Writer::frame(b'C', &body.into_inner())
437}
438
439fn encode_error_or_notice(tag: u8, message: &ErrorOrNotice) -> Result<Vec<u8>, PgWireError> {
440    if message.code.len() != 5
441        || !message
442            .code
443            .bytes()
444            .all(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit())
445    {
446        return Err(PgWireError::InvalidSqlState {
447            code: message.code.clone(),
448        });
449    }
450    let mut body = Writer::new();
451    write_field(&mut body, b'S', message.severity.as_str(), "severity")?;
452    write_field(&mut body, b'V', message.severity.as_str(), "severity")?;
453    write_field(&mut body, b'C', &message.code, "SQLSTATE")?;
454    write_field(&mut body, b'M', &message.message, "error message")?;
455    write_optional_field(&mut body, b'D', message.detail.as_deref(), "error detail")?;
456    write_optional_field(&mut body, b'H', message.hint.as_deref(), "error hint")?;
457    write_optional_i32_field(&mut body, b'P', message.position, "error position")?;
458    write_optional_field(&mut body, b'W', message.where_.as_deref(), "error context")?;
459    write_optional_field(&mut body, b's', message.schema.as_deref(), "error schema")?;
460    write_optional_field(&mut body, b't', message.table.as_deref(), "error table")?;
461    write_optional_field(&mut body, b'c', message.column.as_deref(), "error column")?;
462    write_optional_field(
463        &mut body,
464        b'd',
465        message.data_type.as_deref(),
466        "error data type",
467    )?;
468    write_optional_field(
469        &mut body,
470        b'n',
471        message.constraint.as_deref(),
472        "error constraint",
473    )?;
474    write_optional_field(&mut body, b'F', message.file.as_deref(), "error file")?;
475    write_optional_i32_field(&mut body, b'L', message.line, "error line")?;
476    write_optional_field(&mut body, b'R', message.routine.as_deref(), "error routine")?;
477    body.write_byte(0);
478    Writer::frame(tag, &body.into_inner())
479}
480
481fn encode_parameter_description(oids: &[u32]) -> Result<Vec<u8>, PgWireError> {
482    let mut body = Writer::new();
483    body.write_i16(i16_len(oids.len(), "ParameterDescription parameter")?);
484    for oid in oids {
485        body.write_u32(*oid);
486    }
487    Writer::frame(b't', &body.into_inner())
488}
489
490fn encode_copy_response(tag: u8, response: &CopyResponse) -> Result<Vec<u8>, PgWireError> {
491    if response.overall_format == FormatCode::Text {
492        if let Some((index, _)) = response
493            .column_formats
494            .iter()
495            .enumerate()
496            .find(|(_, format)| **format == FormatCode::Binary)
497        {
498            return Err(PgWireError::BinaryColumnInTextCopy { column: index + 1 });
499        }
500    }
501    let mut body = Writer::new();
502    body.write_byte(match response.overall_format {
503        FormatCode::Text => 0,
504        FormatCode::Binary => 1,
505    });
506    body.write_i16(i16_len(
507        response.column_formats.len(),
508        "CopyResponse column",
509    )?);
510    for format in &response.column_formats {
511        body.write_format(*format);
512    }
513    Writer::frame(tag, &body.into_inner())
514}
515
516fn encode_function_call_response(value: Option<&[u8]>) -> Result<Vec<u8>, PgWireError> {
517    let mut body = Writer::new();
518    match value {
519        Some(bytes) => {
520            body.write_i32(i32_len(bytes.len(), "FunctionCallResponse value")?);
521            body.write_bytes(bytes);
522        }
523        None => body.write_i32(-1),
524    }
525    Writer::frame(b'V', &body.into_inner())
526}
527
528fn encode_notification_response(
529    notification: &NotificationResponse,
530) -> Result<Vec<u8>, PgWireError> {
531    let mut body = Writer::new();
532    body.write_i32(notification.process_id);
533    body.write_cstring(&notification.channel, "NotificationResponse channel")?;
534    body.write_cstring(&notification.payload, "NotificationResponse payload")?;
535    Writer::frame(b'A', &body.into_inner())
536}
537
538fn encode_empty_body(tag: u8) -> Result<Vec<u8>, PgWireError> {
539    Writer::frame(tag, &[])
540}
541
542fn write_field(
543    body: &mut Writer,
544    code: u8,
545    value: &str,
546    context: &'static str,
547) -> Result<(), PgWireError> {
548    body.write_byte(code);
549    body.write_cstring(value, context)
550}
551
552fn write_optional_field(
553    body: &mut Writer,
554    code: u8,
555    value: Option<&str>,
556    context: &'static str,
557) -> Result<(), PgWireError> {
558    if let Some(value) = value {
559        write_field(body, code, value, context)?;
560    }
561    Ok(())
562}
563
564fn write_optional_i32_field(
565    body: &mut Writer,
566    code: u8,
567    value: Option<i32>,
568    context: &'static str,
569) -> Result<(), PgWireError> {
570    if let Some(value) = value {
571        write_field(body, code, &value.to_string(), context)?;
572    }
573    Ok(())
574}