use std::borrow::Cow;
use std::ops::Range;
use bytes::Bytes;
use crate::message::FormatCode;
#[derive(Debug, Clone)]
pub struct DataRow {
pub(super) body: Bytes,
pub(super) column_count: u16,
}
impl DataRow {
pub fn column_count(&self) -> u16 {
self.column_count
}
pub fn column(&self, idx: usize) -> Option<&[u8]> {
if idx >= self.column_count as usize {
return None;
}
let mut offset = 2; for i in 0..=idx {
if offset + 4 > self.body.len() {
return None;
}
let len = i32::from_be_bytes([
self.body[offset],
self.body[offset + 1],
self.body[offset + 2],
self.body[offset + 3],
]);
offset += 4;
if i == idx {
if len < 0 {
return None; }
let len = len as usize;
if offset + len > self.body.len() {
return None;
}
return Some(&self.body[offset..offset + len]);
}
if len >= 0 {
offset += len as usize;
}
}
None
}
pub fn is_null(&self, idx: usize) -> bool {
if idx >= self.column_count as usize {
return false;
}
let mut offset = 2; for i in 0..=idx {
if offset + 4 > self.body.len() {
return false;
}
let len = i32::from_be_bytes([
self.body[offset],
self.body[offset + 1],
self.body[offset + 2],
self.body[offset + 3],
]);
offset += 4;
if i == idx {
return len < 0;
}
if len >= 0 {
offset += len as usize;
}
}
false
}
}
#[derive(Clone)]
pub struct RowDescription {
pub(super) body: Bytes,
pub(super) column_names: Vec<Range<usize>>,
}
impl std::fmt::Debug for RowDescription {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RowDescription")
.field("column_count", &self.column_count())
.finish_non_exhaustive()
}
}
impl RowDescription {
pub fn column_count(&self) -> u16 {
self.column_names.len() as u16
}
pub fn column_name(&self, idx: usize) -> Option<Cow<'_, str>> {
let range = self.column_names.get(idx)?;
Some(String::from_utf8_lossy(&self.body[range.clone()]))
}
pub fn table_oid(&self, idx: usize) -> Option<u32> {
let fixed_offset = self.fixed_data_offset(idx)?;
Some(u32::from_be_bytes([
self.body[fixed_offset],
self.body[fixed_offset + 1],
self.body[fixed_offset + 2],
self.body[fixed_offset + 3],
]))
}
pub fn column_id(&self, idx: usize) -> Option<u16> {
let fixed_offset = self.fixed_data_offset(idx)?;
Some(u16::from_be_bytes([
self.body[fixed_offset + 4],
self.body[fixed_offset + 5],
]))
}
pub fn type_oid(&self, idx: usize) -> Option<u32> {
let fixed_offset = self.fixed_data_offset(idx)?;
Some(u32::from_be_bytes([
self.body[fixed_offset + 6],
self.body[fixed_offset + 7],
self.body[fixed_offset + 8],
self.body[fixed_offset + 9],
]))
}
pub fn type_size(&self, idx: usize) -> Option<i16> {
let fixed_offset = self.fixed_data_offset(idx)?;
Some(i16::from_be_bytes([
self.body[fixed_offset + 10],
self.body[fixed_offset + 11],
]))
}
pub fn type_modifier(&self, idx: usize) -> Option<i32> {
let fixed_offset = self.fixed_data_offset(idx)?;
Some(i32::from_be_bytes([
self.body[fixed_offset + 12],
self.body[fixed_offset + 13],
self.body[fixed_offset + 14],
self.body[fixed_offset + 15],
]))
}
pub fn format(&self, idx: usize) -> Option<FormatCode> {
let fixed_offset = self.fixed_data_offset(idx)?;
let code = u16::from_be_bytes([self.body[fixed_offset + 16], self.body[fixed_offset + 17]]);
Some(if code == 0 {
FormatCode::Text
} else {
FormatCode::Binary
})
}
fn fixed_data_offset(&self, idx: usize) -> Option<usize> {
let range = self.column_names.get(idx)?;
Some(range.end + 1) }
}
#[derive(Clone)]
pub struct CommandComplete {
pub(super) body: Bytes,
pub(super) tag_len: usize,
}
impl std::fmt::Debug for CommandComplete {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CommandComplete")
.field("tag", &self.tag())
.finish()
}
}
impl CommandComplete {
pub fn tag(&self) -> Cow<'_, str> {
String::from_utf8_lossy(&self.body[..self.tag_len])
}
pub fn rows_affected(&self) -> Option<u64> {
let tag = self.tag();
tag.split_whitespace().last().and_then(|s| s.parse().ok())
}
}
#[derive(Clone)]
pub struct NotificationResponse {
pub(super) body: Bytes,
pub(super) channel: Range<usize>,
pub(super) payload: Range<usize>,
}
impl std::fmt::Debug for NotificationResponse {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NotificationResponse")
.field("process_id", &self.process_id())
.field("channel", &self.channel())
.field("payload", &self.payload())
.finish()
}
}
impl NotificationResponse {
pub fn process_id(&self) -> u32 {
u32::from_be_bytes([self.body[0], self.body[1], self.body[2], self.body[3]])
}
pub fn channel(&self) -> Cow<'_, str> {
String::from_utf8_lossy(&self.body[self.channel.clone()])
}
pub fn payload(&self) -> Cow<'_, str> {
String::from_utf8_lossy(&self.body[self.payload.clone()])
}
}
#[derive(Debug, Clone)]
pub struct ParameterDescription {
pub(super) body: Bytes,
pub(super) param_count: u16,
}
impl ParameterDescription {
pub fn param_count(&self) -> u16 {
self.param_count
}
pub fn param_oid(&self, idx: usize) -> Option<u32> {
if idx >= self.param_count as usize {
return None;
}
let offset = 2 + idx * 4;
if offset + 4 > self.body.len() {
return None;
}
Some(u32::from_be_bytes([
self.body[offset],
self.body[offset + 1],
self.body[offset + 2],
self.body[offset + 3],
]))
}
}
#[derive(Clone)]
pub struct ErrorResponse {
pub(super) body: Bytes,
pub(super) local_severity: Range<usize>, pub(super) severity: Range<usize>, pub(super) code: Range<usize>, pub(super) message: Range<usize>, pub(super) detail: Option<Range<usize>>, pub(super) hint: Option<Range<usize>>, pub(super) position: Option<Range<usize>>, pub(super) internal_position: Option<Range<usize>>, pub(super) internal_query: Option<Range<usize>>, pub(super) r#where: Option<Range<usize>>, pub(super) schema: Option<Range<usize>>, pub(super) table: Option<Range<usize>>, pub(super) column: Option<Range<usize>>, pub(super) datatype: Option<Range<usize>>, pub(super) constraint: Option<Range<usize>>, pub(super) file: Option<Range<usize>>, pub(super) line: Option<Range<usize>>, pub(super) routine: Option<Range<usize>>, }
impl std::fmt::Display for ErrorResponse {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"[{}] {}: {}",
self.local_severity(),
self.code(),
self.message()
)
}
}
impl std::fmt::Debug for ErrorResponse {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ErrorResponse")
.field("local_severity", &self.local_severity())
.field("severity", &self.severity())
.field("code", &self.code())
.field("message", &self.message())
.field("detail", &self.detail())
.field("hint", &self.hint())
.field("position", &self.position())
.field("where", &self.r#where())
.field("file", &self.file())
.field("line", &self.line())
.field("routine", &self.routine())
.finish_non_exhaustive()
}
}
impl ErrorResponse {
fn optional_field(&self, range: &Option<Range<usize>>) -> Option<Cow<'_, str>> {
range
.as_ref()
.map(|r| String::from_utf8_lossy(&self.body[r.start..r.end]))
}
pub fn local_severity(&self) -> Cow<'_, str> {
String::from_utf8_lossy(&self.body[self.local_severity.clone()])
}
pub fn severity(&self) -> Cow<'_, str> {
String::from_utf8_lossy(&self.body[self.severity.clone()])
}
pub fn code(&self) -> Cow<'_, str> {
String::from_utf8_lossy(&self.body[self.code.clone()])
}
pub fn message(&self) -> Cow<'_, str> {
String::from_utf8_lossy(&self.body[self.message.clone()])
}
pub fn detail(&self) -> Option<Cow<'_, str>> {
self.optional_field(&self.detail)
}
pub fn hint(&self) -> Option<Cow<'_, str>> {
self.optional_field(&self.hint)
}
pub fn position(&self) -> Option<Cow<'_, str>> {
self.optional_field(&self.position)
}
pub fn internal_position(&self) -> Option<Cow<'_, str>> {
self.optional_field(&self.internal_position)
}
pub fn internal_query(&self) -> Option<Cow<'_, str>> {
self.optional_field(&self.internal_query)
}
pub fn r#where(&self) -> Option<Cow<'_, str>> {
self.optional_field(&self.r#where)
}
pub fn schema(&self) -> Option<Cow<'_, str>> {
self.optional_field(&self.schema)
}
pub fn table(&self) -> Option<Cow<'_, str>> {
self.optional_field(&self.table)
}
pub fn column(&self) -> Option<Cow<'_, str>> {
self.optional_field(&self.column)
}
pub fn datatype(&self) -> Option<Cow<'_, str>> {
self.optional_field(&self.datatype)
}
pub fn constraint(&self) -> Option<Cow<'_, str>> {
self.optional_field(&self.constraint)
}
pub fn file(&self) -> Option<Cow<'_, str>> {
self.optional_field(&self.file)
}
pub fn line(&self) -> Option<Cow<'_, str>> {
self.optional_field(&self.line)
}
pub fn routine(&self) -> Option<Cow<'_, str>> {
self.optional_field(&self.routine)
}
}
pub type NoticeResponse = ErrorResponse;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ReadyForQuery {
pub(super) status: TransactionStatus,
}
impl ReadyForQuery {
pub fn status(&self) -> TransactionStatus {
self.status
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransactionStatus {
Idle,
InTransaction,
Failed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BackendKeyData {
pub(super) process_id: u32,
pub(super) secret_key: u32,
}
impl BackendKeyData {
pub fn process_id(&self) -> u32 {
self.process_id
}
pub fn secret_key(&self) -> u32 {
self.secret_key
}
}
#[derive(Clone)]
pub struct ParameterStatus {
pub(super) body: Bytes,
pub(super) name: Range<usize>,
pub(super) value: Range<usize>,
}
impl std::fmt::Debug for ParameterStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ParameterStatus")
.field("name", &self.name())
.field("value", &self.value())
.finish()
}
}
impl ParameterStatus {
pub fn name(&self) -> Cow<'_, str> {
String::from_utf8_lossy(&self.body[self.name.clone()])
}
pub fn value(&self) -> Cow<'_, str> {
String::from_utf8_lossy(&self.body[self.value.clone()])
}
}
#[derive(Debug, Clone)]
pub struct CopyResponse {
pub(super) body: Bytes,
pub(super) column_count: u16,
}
impl CopyResponse {
pub fn format(&self) -> FormatCode {
if self.body[0] == 0 {
FormatCode::Text
} else {
FormatCode::Binary
}
}
pub fn column_count(&self) -> u16 {
self.column_count
}
pub fn column_format(&self, idx: usize) -> Option<FormatCode> {
if idx >= self.column_count as usize {
return None;
}
let offset = 3 + idx * 2;
if offset + 2 > self.body.len() {
return None;
}
let code = u16::from_be_bytes([self.body[offset], self.body[offset + 1]]);
Some(if code == 0 {
FormatCode::Text
} else {
FormatCode::Binary
})
}
}