use std::fmt;
pub const PROTOCOL_VERSION: i32 = 196_608;
pub const CANCEL_REQUEST_CODE: i32 = 80_877_102;
pub const SSL_REQUEST_CODE: i32 = 80_877_103;
#[derive(Debug, Clone, PartialEq)]
pub enum FrontendMessage {
Startup {
version: i32,
params: Vec<(String, String)>,
},
PasswordMessage(String),
SASLInitialResponse {
mechanism: String,
data: Vec<u8>,
},
SASLResponse(Vec<u8>),
Query(String),
Parse {
name: String,
query: String,
param_types: Vec<u32>,
},
Bind {
portal: String,
statement: String,
param_formats: Vec<i16>,
params: Vec<Option<Vec<u8>>>,
result_formats: Vec<i16>,
},
Describe {
kind: DescribeKind,
name: String,
},
Execute {
portal: String,
max_rows: i32,
},
Close {
kind: DescribeKind,
name: String,
},
Sync,
Flush,
CopyData(Vec<u8>),
CopyDone,
CopyFail(String),
Terminate,
CancelRequest {
process_id: i32,
secret_key: i32,
},
SSLRequest,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DescribeKind {
Statement,
Portal,
}
impl DescribeKind {
pub const fn as_byte(self) -> u8 {
match self {
DescribeKind::Statement => b'S',
DescribeKind::Portal => b'P',
}
}
pub fn from_byte(b: u8) -> Option<Self> {
match b {
b'S' => Some(DescribeKind::Statement),
b'P' => Some(DescribeKind::Portal),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum BackendMessage {
AuthenticationOk,
AuthenticationCleartextPassword,
AuthenticationMD5Password([u8; 4]),
AuthenticationSASL(Vec<String>),
AuthenticationSASLContinue(Vec<u8>),
AuthenticationSASLFinal(Vec<u8>),
BackendKeyData {
process_id: i32,
secret_key: i32,
},
ParameterStatus {
name: String,
value: String,
},
ReadyForQuery(TransactionStatus),
RowDescription(Vec<FieldDescription>),
DataRow(Vec<Option<Vec<u8>>>),
CommandComplete(String),
EmptyQueryResponse,
ParseComplete,
BindComplete,
CloseComplete,
ParameterDescription(Vec<u32>),
NoData,
PortalSuspended,
ErrorResponse(ErrorFields),
NoticeResponse(ErrorFields),
CopyInResponse {
format: i8,
column_formats: Vec<i16>,
},
CopyOutResponse {
format: i8,
column_formats: Vec<i16>,
},
CopyData(Vec<u8>),
CopyDone,
CopyBothResponse {
format: i8,
column_formats: Vec<i16>,
},
NotificationResponse {
process_id: i32,
channel: String,
payload: String,
},
FunctionCallResponse(Option<Vec<u8>>),
NegotiateProtocolVersion {
newest_minor: i32,
unrecognized: Vec<String>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TransactionStatus {
#[default]
Idle,
Transaction,
Error,
}
impl TransactionStatus {
pub const fn as_byte(self) -> u8 {
match self {
TransactionStatus::Idle => b'I',
TransactionStatus::Transaction => b'T',
TransactionStatus::Error => b'E',
}
}
pub fn from_byte(b: u8) -> Option<Self> {
match b {
b'I' => Some(TransactionStatus::Idle),
b'T' => Some(TransactionStatus::Transaction),
b'E' => Some(TransactionStatus::Error),
_ => None,
}
}
}
impl fmt::Display for TransactionStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TransactionStatus::Idle => write!(f, "idle"),
TransactionStatus::Transaction => write!(f, "in transaction"),
TransactionStatus::Error => write!(f, "in failed transaction"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FieldDescription {
pub name: String,
pub table_oid: u32,
pub column_id: i16,
pub type_oid: u32,
pub type_size: i16,
pub type_modifier: i32,
pub format: i16,
}
impl FieldDescription {
pub const fn is_binary(&self) -> bool {
self.format == 1
}
pub const fn is_text(&self) -> bool {
self.format == 0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ErrorFields {
pub severity: String,
pub severity_localized: Option<String>,
pub code: String,
pub message: String,
pub detail: Option<String>,
pub hint: Option<String>,
pub position: Option<i32>,
pub internal_position: Option<i32>,
pub internal_query: Option<String>,
pub where_: Option<String>,
pub schema: Option<String>,
pub table: Option<String>,
pub column: Option<String>,
pub data_type: Option<String>,
pub constraint: Option<String>,
pub file: Option<String>,
pub line: Option<i32>,
pub routine: Option<String>,
}
impl ErrorFields {
pub fn is_fatal(&self) -> bool {
self.severity == "FATAL" || self.severity == "PANIC"
}
pub fn is_error(&self) -> bool {
self.severity == "ERROR"
}
pub fn is_warning(&self) -> bool {
matches!(
self.severity.as_str(),
"WARNING" | "NOTICE" | "DEBUG" | "INFO" | "LOG"
)
}
pub fn error_class(&self) -> &str {
if self.code.len() >= 2 {
&self.code[..2]
} else {
&self.code
}
}
}
impl fmt::Display for ErrorFields {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {} ({})", self.severity, self.message, self.code)?;
if let Some(detail) = &self.detail {
write!(f, "\nDETAIL: {detail}")?;
}
if let Some(hint) = &self.hint {
write!(f, "\nHINT: {hint}")?;
}
if let Some(pos) = self.position {
write!(f, "\nPOSITION: {pos}")?;
}
if let Some(where_) = &self.where_ {
write!(f, "\nCONTEXT: {where_}")?;
}
Ok(())
}
}
pub mod frontend_type {
pub const PASSWORD: u8 = b'p';
pub const QUERY: u8 = b'Q';
pub const PARSE: u8 = b'P';
pub const BIND: u8 = b'B';
pub const DESCRIBE: u8 = b'D';
pub const EXECUTE: u8 = b'E';
pub const CLOSE: u8 = b'C';
pub const SYNC: u8 = b'S';
pub const FLUSH: u8 = b'H';
pub const COPY_DATA: u8 = b'd';
pub const COPY_DONE: u8 = b'c';
pub const COPY_FAIL: u8 = b'f';
pub const TERMINATE: u8 = b'X';
}
pub mod backend_type {
pub const AUTHENTICATION: u8 = b'R';
pub const BACKEND_KEY_DATA: u8 = b'K';
pub const PARAMETER_STATUS: u8 = b'S';
pub const READY_FOR_QUERY: u8 = b'Z';
pub const ROW_DESCRIPTION: u8 = b'T';
pub const DATA_ROW: u8 = b'D';
pub const COMMAND_COMPLETE: u8 = b'C';
pub const EMPTY_QUERY: u8 = b'I';
pub const PARSE_COMPLETE: u8 = b'1';
pub const BIND_COMPLETE: u8 = b'2';
pub const CLOSE_COMPLETE: u8 = b'3';
pub const PARAMETER_DESCRIPTION: u8 = b't';
pub const NO_DATA: u8 = b'n';
pub const PORTAL_SUSPENDED: u8 = b's';
pub const ERROR_RESPONSE: u8 = b'E';
pub const NOTICE_RESPONSE: u8 = b'N';
pub const COPY_IN_RESPONSE: u8 = b'G';
pub const COPY_OUT_RESPONSE: u8 = b'H';
pub const COPY_DATA: u8 = b'd';
pub const COPY_DONE: u8 = b'c';
pub const COPY_BOTH_RESPONSE: u8 = b'W';
pub const NOTIFICATION_RESPONSE: u8 = b'A';
pub const FUNCTION_CALL_RESPONSE: u8 = b'V';
pub const NEGOTIATE_PROTOCOL_VERSION: u8 = b'v';
}
pub mod auth_type {
pub const OK: i32 = 0;
pub const KERBEROS_V5: i32 = 2;
pub const CLEARTEXT_PASSWORD: i32 = 3;
pub const MD5_PASSWORD: i32 = 5;
pub const SCM_CREDENTIAL: i32 = 6;
pub const GSS: i32 = 7;
pub const GSS_CONTINUE: i32 = 8;
pub const SSPI: i32 = 9;
pub const SASL: i32 = 10;
pub const SASL_CONTINUE: i32 = 11;
pub const SASL_FINAL: i32 = 12;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transaction_status_roundtrip() {
for status in [
TransactionStatus::Idle,
TransactionStatus::Transaction,
TransactionStatus::Error,
] {
let byte = status.as_byte();
let parsed = TransactionStatus::from_byte(byte).unwrap();
assert_eq!(status, parsed);
}
}
#[test]
fn test_describe_kind_roundtrip() {
for kind in [DescribeKind::Statement, DescribeKind::Portal] {
let byte = kind.as_byte();
let parsed = DescribeKind::from_byte(byte).unwrap();
assert_eq!(kind, parsed);
}
}
#[test]
fn test_error_fields_display() {
let err = ErrorFields {
severity: "ERROR".to_string(),
code: "23505".to_string(),
message: "duplicate key value violates unique constraint".to_string(),
detail: Some("Key (id)=(1) already exists.".to_string()),
hint: None,
..Default::default()
};
let display = format!("{err}");
assert!(display.contains("ERROR"));
assert!(display.contains("23505"));
assert!(display.contains("duplicate key"));
assert!(display.contains("Key (id)=(1)"));
}
#[test]
fn test_error_fields_classification() {
let fatal = ErrorFields {
severity: "FATAL".to_string(),
code: "XX000".to_string(),
message: "internal error".to_string(),
..Default::default()
};
assert!(fatal.is_fatal());
assert!(!fatal.is_error());
assert!(!fatal.is_warning());
let error = ErrorFields {
severity: "ERROR".to_string(),
code: "23505".to_string(),
message: "constraint violation".to_string(),
..Default::default()
};
assert!(!error.is_fatal());
assert!(error.is_error());
assert!(!error.is_warning());
let warning = ErrorFields {
severity: "WARNING".to_string(),
code: "01000".to_string(),
message: "deprecated feature".to_string(),
..Default::default()
};
assert!(!warning.is_fatal());
assert!(!warning.is_error());
assert!(warning.is_warning());
}
#[test]
fn test_error_class() {
let err = ErrorFields {
code: "23505".to_string(),
..Default::default()
};
assert_eq!(err.error_class(), "23");
}
#[test]
fn test_field_description_format() {
let text_field = FieldDescription {
name: "id".to_string(),
table_oid: 0,
column_id: 0,
type_oid: 23,
type_size: 4,
type_modifier: -1,
format: 0,
};
assert!(text_field.is_text());
assert!(!text_field.is_binary());
let binary_field = FieldDescription {
format: 1,
..text_field
};
assert!(!binary_field.is_text());
assert!(binary_field.is_binary());
}
}