use std::collections::HashMap;
use std::sync::Arc;
use bytes::Bytes;
use crate::error::{Error, Result};
use crate::protocol::backend::{DataRowColumns, FieldDescription};
use crate::types::FromSql;
#[derive(Debug, Clone)]
pub struct RowDescription {
fields: Vec<FieldDescription>,
name_index: HashMap<String, usize>,
}
impl RowDescription {
pub fn new(fields: Vec<FieldDescription>) -> Self {
let name_index = fields
.iter()
.enumerate()
.map(|(i, f)| (f.name.clone(), i))
.collect();
Self { fields, name_index }
}
pub fn len(&self) -> usize {
self.fields.len()
}
pub fn is_empty(&self) -> bool {
self.fields.is_empty()
}
pub fn field(&self, idx: usize) -> Option<&FieldDescription> {
self.fields.get(idx)
}
pub fn column_index(&self, name: &str) -> Option<usize> {
self.name_index.get(name).copied()
}
pub fn fields(&self) -> &[FieldDescription] {
&self.fields
}
}
#[derive(Debug)]
pub struct Row {
columns: DataRowColumns,
description: Arc<RowDescription>,
}
impl Row {
pub fn new(columns: DataRowColumns, description: Arc<RowDescription>) -> Self {
Self {
columns,
description,
}
}
pub fn get<T: FromSql>(&self, idx: usize) -> T {
self.try_get(idx)
.unwrap_or_else(|e| panic!("error getting column {idx}: {e}"))
}
pub fn get_by_name<T: FromSql>(&self, name: &str) -> T {
self.try_get_by_name(name)
.unwrap_or_else(|e| panic!("error getting column '{name}': {e}"))
}
pub fn try_get<T: FromSql>(&self, idx: usize) -> Result<T> {
if idx >= self.columns.len() {
return Err(Error::ColumnIndex {
index: idx,
count: self.columns.len(),
});
}
let raw = self.columns.get(idx);
T::from_sql_nullable(raw.as_deref())
}
pub fn try_get_by_name<T: FromSql>(&self, name: &str) -> Result<T> {
let idx = self
.description
.column_index(name)
.ok_or_else(|| Error::ColumnNotFound(name.to_string()))?;
self.try_get(idx)
}
pub fn get_raw(&self, idx: usize) -> Option<Bytes> {
self.columns.get(idx)
}
pub fn is_null(&self, idx: usize) -> bool {
self.columns.is_null(idx)
}
pub fn len(&self) -> usize {
self.columns.len()
}
pub fn is_empty(&self) -> bool {
self.columns.is_empty()
}
pub fn description(&self) -> &RowDescription {
&self.description
}
}
pub fn parse_command_tag(tag: &str) -> CommandResult {
let parts: Vec<&str> = tag.split_whitespace().collect();
let command = parts.first().copied().unwrap_or("");
let rows_affected = match command {
"INSERT" => parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(0),
"SELECT" | "UPDATE" | "DELETE" | "COPY" | "MERGE" | "MOVE" | "FETCH" => {
parts.last().and_then(|s| s.parse().ok()).unwrap_or(0)
}
_ => 0,
};
CommandResult {
command: command.to_string(),
rows_affected,
}
}
#[derive(Debug, Clone)]
pub struct CommandResult {
pub command: String,
pub rows_affected: u64,
}
#[derive(Debug, Clone)]
pub enum SimpleQueryMessage {
Row(SimpleQueryRow),
CommandComplete(u64),
}
#[derive(Debug, Clone)]
pub struct SimpleQueryRow {
columns: Vec<Option<String>>,
}
impl SimpleQueryRow {
pub fn new(columns: Vec<Option<String>>) -> Self {
Self { columns }
}
pub fn get(&self, idx: usize) -> Option<&str> {
self.columns.get(idx).and_then(|c| c.as_deref())
}
pub fn try_get(&self, idx: usize) -> Result<&str> {
if idx >= self.columns.len() {
return Err(Error::ColumnIndex {
index: idx,
count: self.columns.len(),
});
}
self.columns[idx]
.as_deref()
.ok_or_else(|| Error::Decode("unexpected NULL in simple query row".into()))
}
pub fn len(&self) -> usize {
self.columns.len()
}
pub fn is_empty(&self) -> bool {
self.columns.is_empty()
}
}