use crate::pq::ToArray;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Array<T> {
ndim: usize,
elemtype: crate::pq::Type,
has_nulls: bool,
dimensions: Vec<i32>,
lower_bounds: Vec<i32>,
data: Vec<T>,
}
impl<T: crate::FromSql> Array<T> {
fn shift_idx(&self, indices: &[i32]) -> usize {
if self.dimensions.len() != indices.len() {
panic!();
}
let mut acc = 0;
let mut stride = 1;
for (x, idx) in indices.iter().enumerate().rev() {
let dimension = self.dimensions[x];
let lower_bounds = self.lower_bounds[x] - 1;
let shifted = idx - lower_bounds;
acc += shifted * stride;
stride *= dimension;
}
acc as usize
}
}
impl<T: crate::FromSql> Iterator for Array<T> {
type Item = T;
fn next(&mut self) -> Option<T> {
if self.data.is_empty() {
None
} else {
Some(self.data.remove(0))
}
}
}
macro_rules! tuple_impls {
($($name:ident : $t:ty),+) => {
impl<T: crate::FromSql> std::ops::Index<($($t,)+)> for Array<T> {
type Output = T;
fn index(&self, ($($name,)+): ($($t,)+)) -> &Self::Output {
let index = self.shift_idx(&[$($name,)+]);
&self.data[index]
}
}
}
}
tuple_impls!(a: i32);
tuple_impls!(a: i32, b: i32);
tuple_impls!(a: i32, b: i32, c: i32);
tuple_impls!(a: i32, b: i32, c: i32, d: i32);
tuple_impls!(a: i32, b: i32, c: i32, d: i32, e: i32);
tuple_impls!(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32);
tuple_impls!(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32);
tuple_impls!(
a: i32,
b: i32,
c: i32,
d: i32,
e: i32,
f: i32,
g: i32,
h: i32
);
tuple_impls!(
a: i32,
b: i32,
c: i32,
d: i32,
e: i32,
f: i32,
g: i32,
h: i32,
i: i32
);
impl<T: crate::FromSql> std::ops::Index<i32> for Array<T> {
type Output = T;
fn index(&self, index: i32) -> &Self::Output {
self.index((index,))
}
}
impl<T: crate::FromSql> crate::FromSql for Array<T> {
fn from_text(ty: &crate::pq::Type, raw: Option<&str>) -> crate::Result<Self> {
let raw = crate::not_null(raw)?;
let mut has_nulls = false;
let mut dimensions = Vec::new();
let mut lower_bounds = Vec::new();
let mut data = Vec::new();
let elemtype = ty.elementype();
let mut current = String::new();
let mut it = raw.chars().peekable();
#[allow(clippy::while_let_on_iterator)]
while let Some(c) = it.next() {
match c {
'[' => (),
':' => {
lower_bounds.push(current.parse()?);
current = String::new();
}
']' => {
let lower_bound = lower_bounds.last().unwrap_or(&0);
dimensions.push(current.parse::<i32>()? - lower_bound + 1);
current = String::new();
}
'0'..='9' | '-' => current.push(c),
_ => break,
}
}
#[allow(clippy::while_let_on_iterator)]
while let Some(c) = it.next() {
match c {
'{' => current = String::new(),
',' | '}' => {
if !current.is_empty() {
let value = if current.eq_ignore_ascii_case("null") {
has_nulls = true;
None
} else if current.eq_ignore_ascii_case("'null'") {
Some(current.trim_matches('\''))
} else {
Some(current.as_str())
};
data.push(T::from_text(&elemtype, value)?);
current = String::new();
}
}
_ => current.push(c),
}
}
let array = Self {
ndim: dimensions.len(),
elemtype,
has_nulls,
dimensions,
lower_bounds,
data,
};
Ok(array)
}
fn from_binary(_: &crate::pq::Type, raw: Option<&[u8]>) -> crate::Result<Self> {
use std::io::Read;
let mut buf = crate::not_null(raw)?;
let ndim = crate::from_sql::read_i32(&mut buf)?;
if ndim < 0 {
panic!("Invalid array");
}
let has_nulls = crate::from_sql::read_i32(&mut buf)? != 0;
let oid = crate::from_sql::read_u32(&mut buf)?;
let elemtype: crate::pq::Type = oid.try_into().unwrap_or(crate::pq::Type {
oid,
descr: "Custom type",
name: "custom",
kind: libpq::types::Kind::Composite,
});
let mut dimensions = Vec::new();
let mut lower_bounds = Vec::new();
for _ in 0..ndim {
let dimension = crate::from_sql::read_i32(&mut buf)?;
dimensions.push(dimension);
let lower_bound = crate::from_sql::read_i32(&mut buf)?;
lower_bounds.push(lower_bound);
}
let mut data = Vec::new();
while !buf.is_empty() {
let len = crate::from_sql::read_u32(&mut buf)? as usize;
let value = if len == 0xFFFF_FFFF {
None
} else {
let mut data = vec![0; len];
buf.read_exact(data.as_mut_slice())?;
if data.eq_ignore_ascii_case(b"'null'") {
data.remove(0);
data.pop();
}
Some(data)
};
let element = T::from_sql(&elemtype, crate::pq::Format::Binary, value.as_deref())?;
data.push(element);
}
let array = Self {
ndim: ndim as usize,
elemtype,
has_nulls,
dimensions,
lower_bounds,
data,
};
Ok(array)
}
}
impl<T: crate::ToSql> crate::ToSql for Array<T> {
fn ty(&self) -> crate::pq::Type {
self.elemtype.to_array()
}
fn to_text(&self) -> crate::Result<Option<String>> {
if self.data.is_empty() {
return "{}".to_text();
}
let mut data = String::new();
let need_dims = self
.lower_bounds
.iter()
.fold(false, |acc, x| acc | (*x != 1));
if need_dims {
for (dim, lb) in self.dimensions.iter().zip(&self.lower_bounds) {
let hb = lb + dim - 1;
data.push_str(&format!("[{lb}:{hb}]"));
}
data.push('=');
}
data.push('{');
let mut indx = vec![0; self.ndim];
let mut j = 0;
let mut k = 0;
'outer: loop {
data.push_str(&(0..self.ndim - 1 - j).map(|_| "{").collect::<String>());
let element = &self.data[k];
let raw = element.to_text()?.map_or_else(
|| "null".to_string(),
|mut x| {
if element.ty().is_text() && x.eq_ignore_ascii_case("null") {
x.insert(0, '\'');
x.push('\'');
}
x
},
);
data.push_str(&raw);
k += 1;
for i in (0..self.ndim).rev() {
j = i;
indx[i] += 1;
if indx[i] < self.dimensions[i] {
data.push(',');
break;
}
indx[i] = 0;
data.push('}');
if i == 0 {
break 'outer;
}
}
}
Ok(Some(data))
}
fn to_binary(&self) -> crate::Result<Option<Vec<u8>>> {
let mut buf = Vec::new();
crate::to_sql::write_i32(&mut buf, self.ndim as i32)?;
crate::to_sql::write_i32(&mut buf, self.has_nulls as i32)?;
crate::to_sql::write_i32(&mut buf, self.ty().elementype().oid as i32)?;
for x in 0..self.ndim {
crate::to_sql::write_i32(&mut buf, self.dimensions[x])?;
crate::to_sql::write_i32(&mut buf, self.lower_bounds[x])?;
}
for d in &self.data {
if let Some(raw) = d.to_binary()? {
crate::to_sql::write_i32(&mut buf, raw.len() as i32)?;
buf.extend(&raw);
} else {
crate::to_sql::write_i32(&mut buf, -1)?;
}
}
Ok(Some(buf))
}
}
impl<T: crate::FromSql + crate::ToSql> crate::entity::Simple for Array<T> {}
impl<T: crate::FromSql> From<Array<T>> for Vec<T> {
fn from(array: Array<T>) -> Self {
if array.ndim > 1 {
panic!(
"Unable to transform {} dimension array as vector",
array.ndim
);
}
array.collect()
}
}
impl<T: crate::ToSql + Clone> From<&Vec<T>> for Array<T> {
fn from(data: &Vec<T>) -> Self {
use crate::ToSql;
Self {
ndim: 1,
elemtype: data.ty(),
dimensions: vec![data.len() as i32],
lower_bounds: vec![1],
has_nulls: false,
data: data.clone(),
}
}
}
impl<T: crate::ToSql + Clone> crate::ToSql for Vec<T> {
fn ty(&self) -> crate::pq::Type {
for data in self {
let ty = data.ty().to_array();
if ty != crate::pq::types::UNKNOWN {
return ty;
}
}
crate::pq::types::UNKNOWN
}
fn to_text(&self) -> crate::Result<Option<String>> {
crate::sql::Array::from(self).to_text()
}
fn to_binary(&self) -> crate::Result<Option<Vec<u8>>> {
crate::sql::Array::from(self).to_binary()
}
}
impl<T: crate::FromSql> crate::FromSql for Vec<T> {
fn from_text(ty: &crate::pq::Type, raw: Option<&str>) -> crate::Result<Self> {
Ok(crate::Array::from_text(ty, raw)?.into())
}
fn from_binary(ty: &crate::pq::Type, raw: Option<&[u8]>) -> crate::Result<Self> {
Ok(crate::Array::from_binary(ty, raw)?.into())
}
}
#[cfg(test)]
mod test {
use crate::ToSql;
#[test]
fn array_from_vec() {
let array = crate::Array::from(&vec![1, 2, 3]);
assert_eq!(array.ndim, 1);
assert_eq!(array[2], 3);
}
#[test]
fn vec_to_text() {
let vec = vec![1, 2, 3];
assert_eq!(vec.to_text().unwrap(), Some("{1,2,3}".to_string()));
}
#[test]
fn empty_vec() {
let vec = Vec::<String>::new();
assert_eq!(vec.to_text().unwrap(), Some("{}".to_string()));
}
#[test]
fn array_index() {
let array = crate::Array {
ndim: 2,
elemtype: crate::pq::types::INT8,
has_nulls: false,
dimensions: vec![3, 2],
lower_bounds: vec![1, 1],
data: vec![1, 2, 3, 4, 5, 6],
};
assert_eq!(array[(2, 1)], 6);
}
crate::sql_test!(_int4, Vec<i32>, [("'{1, 2}'", vec![1, 2]),]);
crate::sql_test!(
_int8,
crate::Array<i64>,
[(
"'[1:1][-2:-1][3:5]={{{1,2,3},{4,5,6}}}'",
crate::Array {
ndim: 3,
elemtype: crate::pq::types::INT8,
has_nulls: false,
dimensions: vec![1, 2, 3],
lower_bounds: vec![1, -2, 3],
data: vec![1, 2, 3, 4, 5, 6],
}
)]
);
crate::sql_test!(
_float4,
Vec<Option<f32>>,
[("'{null, 2.}'", vec![None, Some(2.)]),]
);
crate::sql_test!(
_varchar,
Vec<Option<String>>,
[(
"'{str, null, \'\'null\'\', \'\'NuLl\'\', \'\'abcd\'\'}'",
vec![
Some("str".to_string()),
None,
Some("null".to_string()),
Some("NuLl".to_string()),
Some("'abcd'".to_string())
]
)]
);
}