use std::ffi::CStr;
use std::slice;
use std::str;
use std::sync::Arc;
use quex_pq_sys as ffi;
use super::error::{Error, Result};
use super::runtime::{
ResultHandle, decode_value, parse_binary_date, parse_binary_datetime, parse_binary_datetimetz,
parse_binary_f64, parse_binary_i64, parse_binary_time, parse_binary_u64, parse_binary_uuid,
parse_i64_ascii, parse_number, parse_text_date, parse_text_datetime, parse_text_datetimetz,
parse_text_time, parse_text_uuid, parse_u64_ascii,
};
use super::value::{DateTimeTzValue, DateTimeValue, DateValue, TimeValue, Value};
#[derive(Debug, Clone, PartialEq)]
pub struct Column {
pub name: String,
pub type_oid: u32,
pub format: i32,
pub nullable: bool,
}
#[derive(Clone)]
pub(crate) struct Metadata {
pub(crate) columns: Vec<Column>,
pub(crate) name_order: Box<[usize]>,
}
impl Metadata {
pub(crate) fn from_result(result: ResultHandle) -> Self {
unsafe {
let count = ffi::PQnfields(result.as_ptr()) as usize;
let mut columns = Vec::with_capacity(count);
for index in 0..count {
let name_ptr = ffi::PQfname(result.as_ptr(), index as i32);
let name = if name_ptr.is_null() {
String::new()
} else {
CStr::from_ptr(name_ptr).to_string_lossy().into_owned()
};
columns.push(Column {
name,
type_oid: ffi::PQftype(result.as_ptr(), index as i32) as u32,
format: ffi::PQfformat(result.as_ptr(), index as i32),
nullable: true,
});
}
let mut name_order: Vec<usize> = (0..count).collect();
name_order
.sort_unstable_by(|&left, &right| columns[left].name.cmp(&columns[right].name));
Self {
columns,
name_order: name_order.into_boxed_slice(),
}
}
}
}
pub struct ResultSet {
result: Option<ResultHandle>,
metadata: Arc<Metadata>,
row_count: i32,
next_row: i32,
}
impl ResultSet {
pub(crate) fn new(result: ResultHandle, metadata: Arc<Metadata>) -> Self {
let row_count = unsafe { ffi::PQntuples(result.as_ptr()) };
Self {
result: Some(result),
metadata,
row_count,
next_row: 0,
}
}
#[inline]
pub fn columns(&self) -> &[Column] {
&self.metadata.columns
}
pub async fn next(&mut self) -> Result<Option<RowRef<'_>>> {
if self.next_row >= self.row_count {
return Ok(None);
}
let row = self.next_row;
self.next_row += 1;
Ok(Some(RowRef {
result: self.result.expect("result handle missing"),
metadata: &self.metadata,
row,
}))
}
}
impl Drop for ResultSet {
fn drop(&mut self) {
if let Some(result) = self.result.take() {
unsafe {
ffi::PQclear(result.as_ptr());
}
}
}
}
pub struct RowRef<'a> {
result: ResultHandle,
metadata: &'a Metadata,
row: i32,
}
impl<'a> RowRef<'a> {
#[inline]
pub fn is_null(&self, index: impl ColumnIndex) -> Result<bool> {
let index = index.index(&self.metadata.columns, &self.metadata.name_order)?;
Ok(unsafe { ffi::PQgetisnull(self.result.as_ptr(), self.row, index as i32) != 0 })
}
#[inline]
pub fn get_str(&self, index: impl ColumnIndex) -> Result<&str> {
str::from_utf8(self.get_bytes(index)?).map_err(|err| Error::new(err.to_string()))
}
#[inline]
pub fn get_bytes(&self, index: impl ColumnIndex) -> Result<&[u8]> {
let index = index.index(&self.metadata.columns, &self.metadata.name_order)?;
if unsafe { ffi::PQgetisnull(self.result.as_ptr(), self.row, index as i32) } != 0 {
return Err(Error::new("column is null"));
}
let ptr = unsafe { ffi::PQgetvalue(self.result.as_ptr(), self.row, index as i32) };
let len =
unsafe { ffi::PQgetlength(self.result.as_ptr(), self.row, index as i32) } as usize;
Ok(unsafe { pq_bytes(ptr.cast::<u8>(), len) })
}
#[inline]
pub fn get_i64(&self, index: impl ColumnIndex) -> Result<i64> {
let index = index.index(&self.metadata.columns, &self.metadata.name_order)?;
let column = &self.metadata.columns[index];
let bytes = self.get_bytes(index)?;
if column.format == 1 {
parse_binary_i64(bytes, column.type_oid)
} else {
parse_i64_ascii(bytes)
}
}
#[inline]
pub fn get_u64(&self, index: impl ColumnIndex) -> Result<u64> {
let index = index.index(&self.metadata.columns, &self.metadata.name_order)?;
let column = &self.metadata.columns[index];
let bytes = self.get_bytes(index)?;
if column.format == 1 {
parse_binary_u64(bytes, column.type_oid)
} else {
parse_u64_ascii(bytes)
}
}
#[inline]
pub fn get_f64(&self, index: impl ColumnIndex) -> Result<f64> {
let index = index.index(&self.metadata.columns, &self.metadata.name_order)?;
let column = &self.metadata.columns[index];
let bytes = self.get_bytes(index)?;
if column.format == 1 {
parse_binary_f64(bytes, column.type_oid)
} else {
parse_number::<f64>(bytes, "f64")
}
}
#[inline]
pub fn get_date(&self, index: impl ColumnIndex) -> Result<DateValue> {
let index = index.index(&self.metadata.columns, &self.metadata.name_order)?;
let column = &self.metadata.columns[index];
let bytes = self.get_bytes(index)?;
if column.format == 1 {
parse_binary_date(bytes)
} else {
parse_text_date(bytes)
}
}
#[inline]
pub fn get_time_value(&self, index: impl ColumnIndex) -> Result<TimeValue> {
let index = index.index(&self.metadata.columns, &self.metadata.name_order)?;
let column = &self.metadata.columns[index];
let bytes = self.get_bytes(index)?;
if column.format == 1 {
parse_binary_time(bytes)
} else {
parse_text_time(bytes)
}
}
#[inline]
pub fn get_datetime(&self, index: impl ColumnIndex) -> Result<DateTimeValue> {
let index = index.index(&self.metadata.columns, &self.metadata.name_order)?;
let column = &self.metadata.columns[index];
let bytes = self.get_bytes(index)?;
if column.format == 1 {
parse_binary_datetime(bytes)
} else {
parse_text_datetime(bytes)
}
}
#[inline]
pub fn get_datetimetz(&self, index: impl ColumnIndex) -> Result<DateTimeTzValue> {
let index = index.index(&self.metadata.columns, &self.metadata.name_order)?;
let column = &self.metadata.columns[index];
let bytes = self.get_bytes(index)?;
if column.format == 1 {
parse_binary_datetimetz(bytes)
} else {
parse_text_datetimetz(bytes)
}
}
#[inline]
pub fn get_uuid(&self, index: impl ColumnIndex) -> Result<[u8; 16]> {
let index = index.index(&self.metadata.columns, &self.metadata.name_order)?;
let column = &self.metadata.columns[index];
let bytes = self.get_bytes(index)?;
if column.format == 1 {
parse_binary_uuid(bytes)
} else {
parse_text_uuid(bytes)
}
}
pub fn to_owned(&self) -> Result<Row> {
let mut values = Vec::with_capacity(self.metadata.columns.len());
for (index, column) in self.metadata.columns.iter().enumerate() {
let value = if self.is_null(index)? {
Value::Null
} else {
decode_value(column, self.get_bytes(index)?)?
};
values.push(value);
}
Ok(Row {
columns: self.metadata.columns.clone(),
values,
})
}
}
pub(crate) unsafe fn pq_bytes<'a>(ptr: *const u8, len: usize) -> &'a [u8] {
if len == 0 {
&[]
} else {
debug_assert!(!ptr.is_null());
unsafe { slice::from_raw_parts(ptr, len) }
}
}
#[derive(Debug, Clone)]
pub struct Row {
pub columns: Vec<Column>,
pub values: Vec<Value>,
}
pub trait ColumnIndex {
fn index(&self, columns: &[Column], name_order: &[usize]) -> Result<usize>;
}
impl ColumnIndex for usize {
fn index(&self, columns: &[Column], _name_order: &[usize]) -> Result<usize> {
if *self < columns.len() {
Ok(*self)
} else {
Err(Error::new(format!("column {} out of bounds", self)))
}
}
}
impl ColumnIndex for &str {
fn index(&self, columns: &[Column], name_order: &[usize]) -> Result<usize> {
match name_order.binary_search_by(|&idx| columns[idx].name.as_str().cmp(self)) {
Ok(pos) => Ok(name_order[pos]),
Err(_) => Err(Error::new(format!("unknown column {}", self))),
}
}
}
unsafe impl Send for ResultSet {}