diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3de70e9..0000000 --- a/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -language: rust -rust: -- nightly -- beta -- stable -addons: - postgresql: 9.4 -script: -- cargo test diff --git a/Cargo.toml b/Cargo.toml index 91e44c3..5fa0f65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,17 @@ [package] name = "postgres_array" -version = "0.7.1" +version = "0.11.1" authors = ["Steven Fackler "] +edition = "2018" license = "MIT" description = "Array support for rust-postgres" repository = "https://github.com/sfackler/rust-postgres-array" -documentation = "https://sfackler.github.io/rust-postgres-array/doc/v0.7.1/postgres_array" [dependencies] -fallible-iterator = "0.1" -postgres = ">= 0.12, < 0.14" -postgres-protocol = "0.1" +bytes = "1.0" +fallible-iterator = "0.2" +postgres-types = "0.2" +postgres-protocol = "0.6" + +[dev-dependencies] +postgres = "0.19" diff --git a/README.md b/README.md index 1ae0889..0008029 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # rust-postgres-array -[![Build Status](https://travis-ci.org/sfackler/rust-postgres-array.svg?branch=master)](https://travis-ci.org/sfackler/rust-postgres-array) +[![CircleCI](https://circleci.com/gh/sfackler/rust-postgres-array.svg?style=shield)](https://circleci.com/gh/sfackler/rust-postgres-array) -[Documentation](https://sfackler.github.io/rust-postgres-array/doc/v0.7.1/postgres_array) +[Documentation](https://docs.rs/postgres_array) Support for PostgreSQL arrays in [rust-postgres](https://github.com/sfackler/rust-postgres). diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..8e1b563 --- /dev/null +++ b/circle.yml @@ -0,0 +1,25 @@ +version: 2 +jobs: + build: + docker: + - image: rust:1.64.0 + - image: postgres:12 + environment: + POSTGRES_PASSWORD: password + steps: + - checkout + - restore_cache: + key: registry + - run: cargo generate-lockfile + - save_cache: + key: registry-{{ epoch }} + paths: + - /usr/local/cargo/registry/index + - restore_cache: + key: dependencies-1.40-{{ checksum "Cargo.lock" }} + - run: cargo test + - save_cache: + key: dependencies-1.40-{{ checksum "Cargo.lock" }} + paths: + - target + - /usr/local/cargo/registry/cache diff --git a/src/array.rs b/src/array.rs index 4a091ff..9886d8d 100644 --- a/src/array.rs +++ b/src/array.rs @@ -1,9 +1,9 @@ +use std::fmt; use std::ops::{Index, IndexMut}; use std::slice; use std::vec; -use std::fmt; -use Dimension; +use crate::Dimension; /// A multi-dimensional array. #[derive(Debug, PartialEq, Eq, Clone)] @@ -13,38 +13,46 @@ pub struct Array { } impl fmt::Display for Array { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { if self.dims.iter().any(|dim| dim.lower_bound != 1) { for dim in &self.dims { - try!(write!(fmt, - "[{}:{}]", - dim.lower_bound, - dim.lower_bound + dim.len - 1)); + write!( + fmt, + "[{}:{}]", + dim.lower_bound, + dim.lower_bound + dim.len - 1 + )?; } - try!(write!(fmt, "=")); + write!(fmt, "=")?; } fmt_helper(0, &self.dims, &mut self.data.iter(), fmt) } } -fn fmt_helper<'a, T, I>(depth: usize, - dims: &[Dimension], - mut data: &mut I, - fmt: &mut fmt::Formatter) - -> fmt::Result - where I: Iterator, - T: 'a + fmt::Display +fn fmt_helper<'a, T, I>( + depth: usize, + dims: &[Dimension], + data: &mut I, + fmt: &mut fmt::Formatter<'_>, +) -> fmt::Result +where + I: Iterator, + T: 'a + fmt::Display, { + if dims.len() == 0 { + return write!(fmt, "{{}}"); + } + if depth == dims.len() { return write!(fmt, "{}", data.next().unwrap()); } - try!(write!(fmt, "{{")); + write!(fmt, "{{")?; for i in 0..dims[depth].len { if i != 0 { - try!(write!(fmt, ",")); + write!(fmt, ",")?; } - try!(fmt_helper(depth + 1, dims, data, fmt)); + fmt_helper(depth + 1, dims, data, fmt)?; } write!(fmt, "}}") } @@ -60,12 +68,14 @@ impl Array { /// Panics if the number of elements provided does not match the number of /// elements specified by the dimensions. pub fn from_parts(data: Vec, dimensions: Vec) -> Array { - assert!((data.is_empty() && dimensions.is_empty()) || - data.len() as i32 == dimensions.iter().fold(1, |acc, i| acc * i.len), - "size mismatch"); + assert!( + (data.is_empty() && dimensions.is_empty()) + || data.len() as i32 == dimensions.iter().fold(1, |acc, i| acc * i.len), + "size mismatch" + ); Array { dims: dimensions, - data: data, + data, } } @@ -73,10 +83,10 @@ impl Array { pub fn from_vec(data: Vec, lower_bound: i32) -> Array { Array { dims: vec![Dimension { - len: data.len() as i32, - lower_bound: lower_bound, - }], - data: data, + len: data.len() as i32, + lower_bound, + }], + data, } } @@ -85,11 +95,13 @@ impl Array { /// For example, the one dimensional array `[1, 2]` would turn into the /// two-dimensional array `[[1, 2]]`. pub fn wrap(&mut self, lower_bound: i32) { - self.dims.insert(0, - Dimension { - len: 1, - lower_bound: lower_bound, - }); + self.dims.insert( + 0, + Dimension { + len: 1, + lower_bound, + }, + ); } /// Consumes another array, appending it to the top level dimension of this @@ -106,8 +118,10 @@ impl Array { /// /// Panics if the dimensions of the two arrays do not match. pub fn push(&mut self, other: Array) { - assert!(self.dims.len() - 1 == other.dims.len(), - "cannot append differently shaped arrays"); + assert!( + self.dims.len() - 1 == other.dims.len(), + "cannot append differently shaped arrays" + ); for (dim1, dim2) in self.dims.iter().skip(1).zip(other.dims.iter()) { assert!(dim1 == dim2, "cannot append differently shaped arrays"); } @@ -135,14 +149,18 @@ impl Array { /// Returns an iterator over references to the elements of the array in the /// higher-dimensional equivalent of row-major order. - pub fn iter<'a>(&'a self) -> Iter<'a, T> { - Iter { inner: self.data.iter() } + pub fn iter(&self) -> Iter<'_, T> { + Iter { + inner: self.data.iter(), + } } /// Returns an iterator over mutable references to the elements of the /// array in the higher-dimensional equivalent of row-major order. - pub fn iter_mut<'a>(&'a mut self) -> IterMut<'a, T> { - IterMut { inner: self.data.iter_mut() } + pub fn iter_mut(&mut self) -> IterMut<'_, T> { + IterMut { + inner: self.data.iter_mut(), + } } /// Returns the underlying data vector for this Array in the @@ -196,8 +214,27 @@ tuple_impl!(a: i32, b: i32, c: i32, d: i32); tuple_impl!(a: i32, b: i32, c: i32, d: i32, e: i32); tuple_impl!(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32); tuple_impl!(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32); -tuple_impl!(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32, h: i32); -tuple_impl!(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32, h: i32, i: i32); +tuple_impl!( + a: i32, + b: i32, + c: i32, + d: i32, + e: i32, + f: i32, + g: i32, + h: i32 +); +tuple_impl!( + a: i32, + b: i32, + c: i32, + d: i32, + e: i32, + f: i32, + g: i32, + h: i32, + i: i32 +); /// Indexes into the `Array`, retrieving a reference to the contained /// value. @@ -262,13 +299,15 @@ impl IntoIterator for Array { type IntoIter = IntoIter; fn into_iter(self) -> IntoIter { - IntoIter { inner: self.data.into_iter() } + IntoIter { + inner: self.data.into_iter(), + } } } /// An iterator over references to values of an `Array` in the /// higher-dimensional equivalent of row-major order. -pub struct Iter<'a, T: 'a> { +pub struct Iter<'a, T> { inner: slice::Iter<'a, T>, } @@ -298,7 +337,7 @@ impl<'a, T: 'a> ExactSizeIterator for Iter<'a, T> { /// An iterator over mutable references to values of an `Array` in the /// higher-dimensional equivalent of row-major order. -pub struct IterMut<'a, T: 'a> { +pub struct IterMut<'a, T> { inner: slice::IterMut<'a, T>, } diff --git a/src/impls.rs b/src/impls.rs index 6eda63a..399a36e 100644 --- a/src/impls.rs +++ b/src/impls.rs @@ -1,31 +1,38 @@ use fallible_iterator::FallibleIterator; -use postgres::types::{Type, Kind, ToSql, FromSql, IsNull, SessionInfo}; -use postgres_protocol::types; use postgres_protocol; +use postgres_protocol::types; +use postgres_types::{to_sql_checked, FromSql, IsNull, Kind, ToSql, Type}; use std::error::Error; -use {Array, Dimension}; +use crate::{Array, Dimension}; +use postgres_types::private::BytesMut; -impl FromSql for Array - where T: FromSql +impl<'de, T> FromSql<'de> for Array +where + T: FromSql<'de>, { - fn from_sql(ty: &Type, raw: &[u8], info: &SessionInfo) -> Result, Box> { + fn from_sql(ty: &Type, raw: &'de [u8]) -> Result, Box> { let element_type = match *ty.kind() { Kind::Array(ref ty) => ty, _ => unreachable!(), }; - let array = try!(types::array_from_sql(raw)); + let array = types::array_from_sql(raw)?; - let dimensions = try!(array.dimensions() + let dimensions = array + .dimensions() .map(|d| { - Dimension { len: d.len, lower_bound: d.lower_bound } + Ok(Dimension { + len: d.len, + lower_bound: d.lower_bound, + }) }) - .collect()); + .collect()?; - let elements = try!(array.values() - .and_then(|v| FromSql::from_sql_nullable(element_type, v, info)) - .collect()); + let elements = array + .values() + .map(|v| FromSql::from_sql_nullable(element_type, v)) + .collect()?; Ok(Array::from_parts(elements, dimensions)) } @@ -39,36 +46,32 @@ impl FromSql for Array } impl ToSql for Array - where T: ToSql +where + T: ToSql, { - fn to_sql(&self, ty: &Type, w: &mut Vec, info: &SessionInfo) -> Result> { + fn to_sql(&self, ty: &Type, w: &mut BytesMut) -> Result> { let element_type = match ty.kind() { &Kind::Array(ref ty) => ty, _ => unreachable!(), }; - let dimensions = self.dimensions() - .iter() - .map(|d| { - types::ArrayDimension { - len: d.len, - lower_bound: d.lower_bound, - } - }); + let dimensions = self.dimensions().iter().map(|d| types::ArrayDimension { + len: d.len, + lower_bound: d.lower_bound, + }); let elements = self.iter(); - try!(types::array_to_sql(dimensions, - true, - element_type.oid(), - elements, - |v, w| { - match v.to_sql(element_type, w, info) { - Ok(IsNull::Yes) => Ok(postgres_protocol::IsNull::Yes), - Ok(IsNull::No) => Ok(postgres_protocol::IsNull::No), - Err(e) => Err(e), - } - }, - w)); + types::array_to_sql( + dimensions, + element_type.oid(), + elements, + |v, w| match v.to_sql(element_type, w) { + Ok(IsNull::Yes) => Ok(postgres_protocol::IsNull::Yes), + Ok(IsNull::No) => Ok(postgres_protocol::IsNull::No), + Err(e) => Err(e), + }, + w, + )?; Ok(IsNull::No) } @@ -87,38 +90,49 @@ impl ToSql for Array mod test { use std::fmt; - use postgres::{Connection, TlsMode}; - use postgres::types::{FromSql, ToSql}; - use Array; + use crate::Array; + use postgres::types::{FromSqlOwned, ToSql}; + use postgres::{Client, NoTls}; - fn test_type(sql_type: &str, - checks: &[(T, S)]) { - let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap(); + fn test_type( + sql_type: &str, + checks: &[(T, S)], + ) { + let mut conn = Client::connect("postgres://postgres:password@localhost", NoTls).unwrap(); for &(ref val, ref repr) in checks.iter() { - let stmt = conn.prepare(&format!("SELECT {}::{}", *repr, sql_type)).unwrap(); - let result = stmt.query(&[]).unwrap().iter().next().unwrap().get(0); + let result = conn + .query(&*format!("SELECT {}::{}", *repr, sql_type), &[]) + .unwrap()[0] + .get(0); assert!(val == &result); - let stmt = conn.prepare(&format!("SELECT $1::{}", sql_type)).unwrap(); - let result = stmt.query(&[val]).unwrap().iter().next().unwrap().get(0); + let result = conn + .query(&*format!("SELECT $1::{}", sql_type), &[val]) + .unwrap()[0] + .get(0); assert!(val == &result); } } macro_rules! test_array_params { - ($name:expr, $v1:expr, $s1:expr, $v2:expr, $s2:expr, $v3:expr, $s3:expr) => ({ - - let tests = &[(Some(Array::from_vec(vec!(Some($v1), Some($v2), None), 1)), - format!("'{{{},{},NULL}}'", $s1, $s2)), - (None, "NULL".to_string())]; + ($name:expr, $v1:expr, $s1:expr, $v2:expr, $s2:expr, $v3:expr, $s3:expr) => {{ + let tests = &[ + ( + Some(Array::from_vec(vec![Some($v1), Some($v2), None], 1)), + format!("'{{{},{},NULL}}'", $s1, $s2), + ), + (None, "NULL".to_string()), + ]; test_type(&format!("{}[]", $name), tests); - let mut a = Array::from_vec(vec!(Some($v1), Some($v2)), 0); + let mut a = Array::from_vec(vec![Some($v1), Some($v2)], 0); a.wrap(-1); - a.push(Array::from_vec(vec!(None, Some($v3)), 0)); - let tests = &[(Some(a), format!("'[-1:0][0:1]={{{{{},{}}},{{NULL,{}}}}}'", - $s1, $s2, $s3))]; + a.push(Array::from_vec(vec![None, Some($v3)], 0)); + let tests = &[( + Some(a), + format!("'[-1:0][0:1]={{{{{},{}}},{{NULL,{}}}}}'", $s1, $s2, $s3), + )]; test_type(&format!("{}[][]", $name), tests); - }) + }}; } #[test] @@ -128,13 +142,15 @@ mod test { #[test] fn test_byteaarray_params() { - test_array_params!("BYTEA", - vec![0u8, 1], - r#""\\x0001""#, - vec![254u8, 255u8], - r#""\\xfeff""#, - vec![10u8, 11u8], - r#""\\x0a0b""#); + test_array_params!( + "BYTEA", + vec![0u8, 1], + r#""\\x0001""#, + vec![254u8, 255u8], + r#""\\xfeff""#, + vec![10u8, 11u8], + r#""\\x0a0b""# + ); } #[test] @@ -144,13 +160,15 @@ mod test { #[test] fn test_namearray_params() { - test_array_params!("NAME", - "hello".to_string(), - "hello", - "world".to_string(), - "world", - "!".to_string(), - "!"); + test_array_params!( + "NAME", + "hello".to_string(), + "hello", + "world".to_string(), + "world", + "!".to_string(), + "!" + ); } #[test] @@ -165,35 +183,41 @@ mod test { #[test] fn test_textarray_params() { - test_array_params!("TEXT", - "hello".to_string(), - "hello", - "world".to_string(), - "world", - "!".to_string(), - "!"); + test_array_params!( + "TEXT", + "hello".to_string(), + "hello", + "world".to_string(), + "world", + "!".to_string(), + "!" + ); } #[test] fn test_charnarray_params() { - test_array_params!("CHAR(5)", - "hello".to_string(), - "hello", - "world".to_string(), - "world", - "! ".to_string(), - "!"); + test_array_params!( + "CHAR(5)", + "hello".to_string(), + "hello", + "world".to_string(), + "world", + "! ".to_string(), + "!" + ); } #[test] fn test_varchararray_params() { - test_array_params!("VARCHAR", - "hello".to_string(), - "hello", - "world".to_string(), - "world", - "!".to_string(), - "!"); + test_array_params!( + "VARCHAR", + "hello".to_string(), + "hello", + "world".to_string(), + "world", + "!".to_string(), + "!" + ); } #[test] @@ -213,8 +237,7 @@ mod test { #[test] fn test_empty_array() { - let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap(); - let stmt = conn.prepare("SELECT '{}'::INT4[]").unwrap(); - stmt.query(&[]).unwrap().iter().next().unwrap().get::<_, Array>(0); + let mut conn = Client::connect("postgres://postgres@localhost", NoTls).unwrap(); + conn.query("SELECT '{}'::INT4[]", &[]).unwrap()[0].get::<_, Array>(0); } } diff --git a/src/lib.rs b/src/lib.rs index e29dbdb..21240da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,8 @@ //! Multi-dimensional arrays with per-dimension specifiable lower bounds -#![doc(html_root_url="https://sfackler.github.io/rust-postgres-array/doc/v0.7.1")] - -extern crate fallible_iterator; -#[macro_use] -extern crate postgres; -extern crate postgres_protocol; +#![doc(html_root_url = "https://docs.rs/postgres_array/0.10")] #[doc(inline)] -pub use array::Array; +pub use crate::array::Array; pub mod array; mod impls; @@ -25,8 +20,10 @@ impl Dimension { fn shift(&self, idx: i32) -> i32 { let offset = self.lower_bound; assert!(idx >= offset, "out of bounds array access"); - assert!(offset >= 0 || idx <= 0 || i32::max_value() - (-offset) >= idx, - "out of bounds array access"); + assert!( + offset >= 0 || idx <= 0 || i32::max_value() - (-offset) >= idx, + "out of bounds array access" + ); match idx.checked_sub(offset) { Some(shifted) => shifted, None => panic!("out of bounds array access"), @@ -41,10 +38,13 @@ mod tests { #[test] fn test_from_vec() { let a = Array::from_vec(vec![0i32, 1, 2], -1); - assert!(&[Dimension { - len: 3, - lower_bound: -1, - }][..] == a.dimensions()); + assert!( + &[Dimension { + len: 3, + lower_bound: -1, + },][..] + == a.dimensions() + ); assert_eq!(0, a[-1]); assert_eq!(1, a[0]); assert_eq!(2, a[1]); @@ -146,5 +146,8 @@ mod tests { a.push(Array::from_vec(vec![4, 5, 6], 3)); a.wrap(1); assert_eq!("[1:1][-2:-1][3:5]={{{1,2,3},{4,5,6}}}", &format!("{}", a)); + + let a: Array = Array::from_parts(vec![], vec![]); + assert_eq!("{}", &format!("{}", a)); } }