diff --git a/src/lib.rs b/src/lib.rs index 1ecd896b..e44294f7 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,6 +136,8 @@ pub use crate::{ pool::Pool, types::{block::Block, Options, Simple}, }; +use crate::types::query_result_owned::QueryResultOwned; +use crate::types::query_result_owned::stream_blocks::BlockStreamOwned; mod binary; mod client_info; @@ -301,7 +303,7 @@ impl Client { }, timeout, ) - .await + .await } } @@ -366,13 +368,13 @@ impl ClientHandle { }, timeout, ) - .await + .await } /// Executes Clickhouse `query` on Conn. pub fn query(&mut self, sql: Q) -> QueryResult - where - Query: From, + where + Query: From, { let query = Query::from(sql); QueryResult { @@ -381,10 +383,22 @@ impl ClientHandle { } } + /// Executes Clickhouse `query` on Conn. + pub fn query_owned(self, sql: Q) -> QueryResultOwned + where + Query: From, + { + let query = Query::from(sql); + QueryResultOwned { + client: self, + query, + } + } + /// Convenience method to prepare and execute a single SQL statement. pub async fn execute(&mut self, sql: Q) -> Result<()> - where - Query: From, + where + Query: From, { let transport = self.execute_(sql).await?; self.inner = Some(transport); @@ -392,8 +406,8 @@ impl ClientHandle { } async fn execute_(&mut self, sql: Q) -> Result - where - Query: From, + where + Query: From, { let timeout = try_opt!(self.context.options.get()) .execute_timeout @@ -429,18 +443,18 @@ impl ClientHandle { Ok(h.unwrap()) } }) - .await + .await }, timeout, ) - .await + .await } /// Convenience method to insert block of data. pub async fn insert(&mut self, table: Q, block: B) -> Result<()> - where - Query: From, - B: AsRef, + where + Query: From, + B: AsRef, { let query = Self::make_query(table, block.as_ref())?; let transport = self.insert_(query.clone(), block.as_ref()).await?; @@ -474,11 +488,11 @@ impl ClientHandle { Self::insert_tail_(transport, context, query, chunks).await } }) - .await + .await }, timeout, ) - .await + .await } async fn insert_tail_( @@ -520,8 +534,8 @@ impl ClientHandle { } fn make_query(table: Q, block: &Block) -> Result - where - Query: From, + where + Query: From, { let mut names: Vec<_> = Vec::with_capacity(block.as_ref().column_count()); for column in block.as_ref().columns() { @@ -532,10 +546,10 @@ impl ClientHandle { } pub(crate) async fn wrap_future(&mut self, f: F) -> Result - where - F: FnOnce(&mut Self) -> R + Send, - R: Future>, - T: 'static, + where + F: FnOnce(&mut Self) -> R + Send, + R: Future>, + T: 'static, { let ping_before_query = try_opt!(self.context.options.get()).ping_before_query; @@ -546,8 +560,8 @@ impl ClientHandle { } pub(crate) fn wrap_stream<'a, F>(&'a mut self, f: F) -> BoxStream<'a, Result> - where - F: (FnOnce(&'a mut Self) -> Result>) + Send + 'static, + where + F: (FnOnce(&'a mut Self) -> Result>) + Send + 'static, { let ping_before_query = match self.context.options.get() { Ok(val) => val.ping_before_query, @@ -575,6 +589,37 @@ impl ClientHandle { } } + pub(crate) fn wrap_stream_owned(mut self, f: F) -> BoxStream<'static, Result> + where + F: (FnOnce(Self) -> Result) + Send + 'static, + { + let ping_before_query = match self.context.options.get() { + Ok(val) => val.ping_before_query, + Err(err) => return Box::pin(stream::once(future::err(err))), + }; + + if ping_before_query { + let fut: BoxFuture<'static, BoxStream<'static, Result>> = Box::pin(async move { + let inner: BoxStream<'static, Result> = if let Err(err) = self.check_connection().await { + Box::pin(stream::once(future::err(err))) + } else { + match f(self) { + Ok(s) => Box::pin(s), + Err(err) => Box::pin(stream::once(future::err(err))), + } + }; + inner + }); + + Box::pin(fut.flatten_stream()) + } else { + match f(self) { + Ok(s) => Box::pin(s), + Err(err) => Box::pin(stream::once(future::err(err))), + } + } + } + /// Check connection and try to reconnect if necessary. pub async fn check_connection(&mut self) -> Result<()> { self.pool.detach(); @@ -626,8 +671,8 @@ fn column_name_to_string(name: &str) -> Result { #[cfg(feature = "async_std")] async fn with_timeout(future: F, duration: Duration) -> F::Output -where - F: Future>, + where + F: Future>, { use async_std::io; use futures_util::future::TryFutureExt; @@ -639,8 +684,8 @@ where #[cfg(not(feature = "async_std"))] async fn with_timeout(future: F, timeout: Duration) -> F::Output -where - F: Future>, + where + F: Future>, { tokio::time::timeout(timeout, future).await? } diff --git a/src/types/column/datetime64.rs b/src/types/column/datetime64.rs index 7aa80d4c..cc249712 100644 --- a/src/types/column/datetime64.rs +++ b/src/types/column/datetime64.rs @@ -156,7 +156,7 @@ pub(crate) fn to_native_datetime_opt(value: i64, precision: u32) -> Option::from_timestamp(sec, nsec as u32).map(|d| d.naive_utc()) } #[cfg(test)] diff --git a/src/types/column/iter/mod.rs b/src/types/column/iter/mod.rs index f34956fc..45b85b4c 100644 --- a/src/types/column/iter/mod.rs +++ b/src/types/column/iter/mod.rs @@ -307,9 +307,9 @@ impl FusedIterator for StringIterator<'_> {} impl<'a> DecimalIterator<'a> { #[inline(always)] unsafe fn next_unchecked_(&mut self) -> Decimal - where - T: Copy + Sized, - i64: From, + where + T: Copy + Sized, + i64: From, { let current_value = *(self.ptr as *const T); self.ptr = (self.ptr as *const T).offset(1) as *const u8; @@ -479,7 +479,7 @@ impl<'a> NativeDateTimeIterator<'a> { match &self.inner { DateTimeInnerIterator::DateTime32(ptr) => { let current_value = *ptr.add(index_); - NaiveDateTime::from_timestamp_opt(i64::from(current_value), 0).unwrap() + DateTime::from_timestamp(i64::from(current_value), 0).unwrap().naive_utc() } DateTimeInnerIterator::DateTime64(ptr, precision) => { let current_value = *ptr.add(index_); @@ -664,8 +664,8 @@ impl<'a> Iterator for DateTimeIterator<'a> { } impl<'a, I> ExactSizeIterator for NullableIterator<'a, I> -where - I: Iterator, + where + I: Iterator, { #[inline(always)] fn len(&self) -> usize { @@ -675,8 +675,8 @@ where } impl<'a, I> Iterator for NullableIterator<'a, I> -where - I: Iterator, + where + I: Iterator, { type Item = Option; @@ -758,8 +758,8 @@ impl<'a, I: Iterator> Iterator for ArrayIterator<'a, I> { impl<'a, I: Iterator> FusedIterator for ArrayIterator<'a, I> {} impl<'a, K: Iterator, V: Iterator> ExactSizeIterator for MapIterator<'a, K, V> -where - K::Item: Eq + Hash, + where + K::Item: Eq + Hash, { #[inline(always)] fn len(&self) -> usize { @@ -768,8 +768,8 @@ where } impl<'a, K: Iterator, V: Iterator> Iterator for MapIterator<'a, K, V> -where - K::Item: Eq + Hash, + where + K::Item: Eq + Hash, { type Item = HashMap; @@ -935,7 +935,7 @@ impl<'a> Iterable<'a, Simple> for &[u8] { return Err(Error::FromSql(FromSqlError::InvalidType { src: column.sql_type().to_string(), dst: SqlType::String.to_string(), - })) + })); } }; @@ -1196,8 +1196,8 @@ fn date_iter(column: &Column, props: u32) -> Result { } impl<'a, T> Iterable<'a, Simple> for Option -where - T: Iterable<'a, Simple>, + where + T: Iterable<'a, Simple>, { type Iter = NullableIterator<'a, T::Iter>; @@ -1238,8 +1238,8 @@ where } impl<'a, T> Iterable<'a, Simple> for Vec -where - T: Iterable<'a, Simple>, + where + T: Iterable<'a, Simple>, { type Iter = ArrayIterator<'a, T::Iter>; @@ -1279,10 +1279,10 @@ where } impl<'a, K, V> Iterable<'a, Simple> for HashMap -where - K: Iterable<'a, Simple>, - <>::Iter as Iterator>::Item: Eq + Hash, - V: Iterable<'a, Simple>, + where + K: Iterable<'a, Simple>, + <>::Iter as Iterator>::Item: Eq + Hash, + V: Iterable<'a, Simple>, { type Iter = MapIterator<'a, K::Iter, V::Iter>; @@ -1328,8 +1328,8 @@ where } pub struct ComplexIterator<'a, T> -where - T: Iterable<'a, Simple>, + where + T: Iterable<'a, Simple>, { column_type: SqlType, @@ -1342,8 +1342,8 @@ where } impl<'a, T> Iterator for ComplexIterator<'a, T> -where - T: Iterable<'a, Simple>, + where + T: Iterable<'a, Simple>, { type Item = <>::Iter as Iterator>::Item; @@ -1392,8 +1392,8 @@ where } impl<'a, T> Iterable<'a, Complex> for T -where - T: Iterable<'a, Simple> + 'a, + where + T: Iterable<'a, Simple> + 'a, { type Iter = ComplexIterator<'a, T>; diff --git a/src/types/from_sql.rs b/src/types/from_sql.rs index a5e580d1..9f672998 100644 --- a/src/types/from_sql.rs +++ b/src/types/from_sql.rs @@ -106,9 +106,9 @@ impl<'a> FromSql<'a> for String { } impl<'a, K, V> FromSql<'a> for HashMap -where - K: FromSql<'a> + Eq + PartialEq + Hash, - V: FromSql<'a>, + where + K: FromSql<'a> + Eq + PartialEq + Hash, + V: FromSql<'a>, { fn from_sql(value: ValueRef<'a>) -> FromSqlResult { if let ValueRef::Map(_k, _v, hm) = value { @@ -274,8 +274,8 @@ from_sql_vec_impl! { } impl<'a, T> FromSql<'a> for Option -where - T: FromSql<'a>, + where + T: FromSql<'a>, { fn from_sql(value: ValueRef<'a>) -> FromSqlResult { match value { @@ -301,7 +301,7 @@ impl<'a> FromSql<'a> for NaiveDate { fn from_sql(value: ValueRef<'a>) -> FromSqlResult { match value { ValueRef::Date(v) => NaiveDate::from_ymd_opt(1970, 1, 1) - .map(|unix_epoch| unix_epoch + Duration::days(v.into())) + .map(|unix_epoch| unix_epoch + Duration::try_days(v.into()).expect("TimeDelta::days out of bounds")) .ok_or(Error::FromSql(FromSqlError::OutOfRange)), _ => { let from = SqlType::from(value).to_string(); diff --git a/src/types/mod.rs b/src/types/mod.rs index 83ff796d..e266449e 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -46,6 +46,7 @@ mod cmd; mod date_converter; mod query; pub(crate) mod query_result; +pub(crate) mod query_result_owned; mod decimal; mod enums; diff --git a/src/types/query_result_owned/mod.rs b/src/types/query_result_owned/mod.rs new file mode 100644 index 00000000..59742e24 --- /dev/null +++ b/src/types/query_result_owned/mod.rs @@ -0,0 +1,125 @@ +use futures_util::{ + future, + stream::{self, BoxStream, StreamExt}, + TryStreamExt, +}; +use log::info; +use std::{marker::PhantomData, sync::Arc}; + +use crate::{ + errors::Result, + try_opt, + types::{ + block::BlockRef, query_result_owned::stream_blocks::BlockStreamOwned, Block, Cmd, Complex, Query, Row, + Rows, Simple, + }, + with_timeout, ClientHandle, +}; + +pub(crate) mod stream_blocks; + +/// Result of a query or statement execution. +pub struct QueryResultOwned { + pub(crate) client: ClientHandle, + pub(crate) query: Query, +} + +impl QueryResultOwned { + /// Fetch data from table. It returns a block that contains all rows. + pub async fn fetch_all(self) -> Result> { + let timeout = try_opt!(self.client.context.options.get()).query_timeout; + + with_timeout( + async { + let blocks = self + .stream_blocks_(false) + .try_fold(Vec::new(), |mut blocks, block| { + if !block.is_empty() { + blocks.push(block); + } + future::ready(Ok(blocks)) + }) + .await?; + Ok(Block::concat(blocks.as_slice())) + }, + timeout, + ) + .await + } + + /// Method that produces a stream of blocks containing rows + /// + /// example: + /// + /// ```rust + /// # use std::env; + /// # use clickhouse_rs::{Pool, errors::Result}; + /// # use futures_util::{future, TryStreamExt}; + /// # + /// # let mut rt = tokio::runtime::Runtime::new().unwrap(); + /// # let ret: Result<()> = rt.block_on(async { + /// # + /// # let database_url = env::var("DATABASE_URL") + /// # .unwrap_or("tcp://localhost:9000?compression=lz4".into()); + /// # + /// # let sql_query = "SELECT number FROM system.numbers LIMIT 100000"; + /// # let pool = Pool::new(database_url); + /// # + /// let mut c = pool.get_handle().await?; + /// let mut result = c.query_owned(sql_query) + /// .stream_blocks() + /// .try_for_each(|block| { + /// println!("{:?}\nblock counts: {} rows", block, block.row_count()); + /// future::ready(Ok(())) + /// }).await?; + /// # Ok(()) + /// # }); + /// # ret.unwrap() + /// ``` + pub fn stream_blocks(self) -> BoxStream<'static, Result> { + self.stream_blocks_(true) + } + + fn stream_blocks_(self, skip_first_block: bool) -> BoxStream<'static, Result> { + let query = self.query.clone(); + + self.client + .wrap_stream_owned::<_>(move |mut c: ClientHandle| { + info!("[send query] {}", query.get_sql()); + c.pool.detach(); + + let context = c.context.clone(); + + let inner = c.get_inner()?.call(Cmd::SendQuery(query, context)); + + Ok(BlockStreamOwned::new(c, inner, skip_first_block)) + }) + } + + /// Method that produces a stream of rows + pub fn stream(self) -> BoxStream<'static, Result>> { + Box::pin( + self.stream_blocks() + .map(|block_ret| { + let result: BoxStream<'static, Result>> = match block_ret { + Ok(block) => { + let block = Arc::new(block); + let block_ref = BlockRef::Owned(block); + + Box::pin( + stream::iter(Rows { + row: 0, + block_ref, + kind: PhantomData, + }) + .map(|row| -> Result> { Ok(row) }), + ) + } + Err(err) => Box::pin(stream::once(future::err(err))), + }; + result + }) + .flatten(), + ) + } +} diff --git a/src/types/query_result_owned/stream_blocks.rs b/src/types/query_result_owned/stream_blocks.rs new file mode 100644 index 00000000..9fdc13c4 --- /dev/null +++ b/src/types/query_result_owned/stream_blocks.rs @@ -0,0 +1,123 @@ +use std::{ + borrow::Cow, + io::ErrorKind, + pin::Pin, + task::{self, Poll}, +}; + +use futures_core::Stream; +use futures_util::StreamExt; + +use crate::{ + errors::{DriverError, Error, Result}, + io::transport::PacketStream, + types::{Block, Packet}, + ClientHandle, +}; + +pub(crate) struct BlockStreamOwned { + client: ClientHandle, + inner: PacketStream, + state: BlockStreamState, + block_index: usize, + skip_first_block: bool, +} + +#[derive(Clone, Copy)] +pub(crate) enum BlockStreamState { + /// Currently reading from block packet stream; some further packets may be pending + Reading, + /// Completely finished reading from block packet stream; connection is now idle + Finished, + /// There was an error reading packet; transport is broken + Error, +} + +impl Drop for BlockStreamOwned { + fn drop(&mut self) { + match self.state { + BlockStreamState::Reading => { + if !self.client.pool.is_attached() { + self.client.pool.attach(); + } + + if let Some(mut transport) = self.inner.take_transport() { + transport.inconsistent = true; + self.client.inner = Some(transport); + } + } + BlockStreamState::Finished => {} + BlockStreamState::Error => { + // drop broken transport; don't return it to pool to prevent pool poisoning + } + } + } +} + +impl BlockStreamOwned { + pub(crate) fn new( + client: ClientHandle, + inner: PacketStream, + skip_first_block: bool, + ) -> BlockStreamOwned { + BlockStreamOwned { + client, + inner, + state: BlockStreamState::Reading, + block_index: 0, + skip_first_block, + } + } +} + +impl Stream for BlockStreamOwned { + type Item = Result; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { + loop { + match self.state { + BlockStreamState::Reading => {} + BlockStreamState::Finished => return Poll::Ready(None), + BlockStreamState::Error => { + return Poll::Ready(Some(Err(Error::Other(Cow::Borrowed( + "Attempt to read from broken transport", + ))))); + } + }; + + let packet = match self.inner.poll_next_unpin(cx) { + Poll::Ready(Some(Err(err))) => return Poll::Ready(Some(Err(err.into()))), + Poll::Pending => return Poll::Pending, + Poll::Ready(None) => { + self.state = BlockStreamState::Error; + return Poll::Ready(Some(Err(Error::Io(std::io::Error::from( + ErrorKind::UnexpectedEof, + ))))); + } + Poll::Ready(Some(Ok(packet))) => packet, + }; + + match packet { + Packet::Eof(inner) => { + self.client.inner = Some(inner); + if !self.client.pool.is_attached() { + self.client.pool.attach(); + } + self.state = BlockStreamState::Finished; + } + Packet::ProfileInfo(_) | Packet::Progress(_) => {} + Packet::Exception(exception) => { + self.state = BlockStreamState::Finished; + return Poll::Ready(Some(Err(Error::Server(exception)))); + } + Packet::Block(block) => { + self.block_index += 1; + if (self.block_index > 1 || !self.skip_first_block) && !block.is_empty() { + return Poll::Ready(Some(Ok(block))); + } + } + _ => return Poll::Ready(Some(Err(Error::Driver(DriverError::UnexpectedPacket)))), + } + } + } +} diff --git a/src/types/value.rs b/src/types/value.rs index 9b38b416..820cc460 100644 --- a/src/types/value.rs +++ b/src/types/value.rs @@ -125,22 +125,22 @@ impl PartialEq for Value { #[rustfmt::skip] const MULTIPLIERS: [i64; 10] = [ 1_000_000_000, // 1 s is 10^9 nanos - 100_000_000, - 10_000_000, - 1_000_000, // 1 ms is 10^6 nanos - 100_000, - 10_000, - 1_000, // 1 µs is 10^3 nanos - 100, - 10, - 1, // 1 ns is 1 nanos! + 100_000_000, + 10_000_000, + 1_000_000, // 1 ms is 10^6 nanos + 100_000, + 10_000, + 1_000, // 1 µs is 10^3 nanos + 100, + 10, + 1, // 1 ns is 1 nanos! ]; // The precision must be in the [0 - 9] range. As such, the // following indexing can not fail. prec_a == prec_b && tz_a.timestamp_nanos(a * MULTIPLIERS[*prec_a as usize]) - == tz_b.timestamp_nanos(b * MULTIPLIERS[*prec_b as usize]) + == tz_b.timestamp_nanos(b * MULTIPLIERS[*prec_b as usize]) } _ => false, @@ -229,13 +229,13 @@ impl fmt::Display for Value { } Value::Date(v) if f.alternate() => { let date = NaiveDate::from_ymd_opt(1970, 1, 1) - .map(|unix_epoch| unix_epoch + Duration::days((*v).into())) + .map(|unix_epoch| unix_epoch + Duration::try_days((*v).into()).expect("TimeDelta::days out of bounds")) .unwrap(); fmt::Display::fmt(&date, f) } Value::Date(v) => { let date = NaiveDate::from_ymd_opt(1970, 1, 1) - .map(|unix_epoch| unix_epoch + Duration::days((*v).into())) + .map(|unix_epoch| unix_epoch + Duration::try_days((*v).into()).expect("TimeDelta::days out of bounds")) .unwrap(); fmt::Display::fmt(&date.format("%Y-%m-%d"), f) } @@ -320,9 +320,9 @@ impl From for SqlType { } impl From> for Value -where - Value: From, - T: HasSqlType, + where + Value: From, + T: HasSqlType, { fn from(value: Option) -> Value { match value { @@ -429,9 +429,9 @@ impl From for Value { } impl From> for Value -where - K: Into + HasSqlType, - V: Into + HasSqlType, + where + K: Into + HasSqlType, + V: Into + HasSqlType, { fn from(hm: HashMap) -> Self { let mut res = HashMap::with_capacity(hm.capacity()); @@ -549,7 +549,7 @@ impl From for AppDate { fn from(v: Value) -> AppDate { if let Value::Date(x) = v { return NaiveDate::from_ymd_opt(1970, 1, 1) - .map(|unix_epoch| unix_epoch + Duration::days(x.into())) + .map(|unix_epoch| unix_epoch + Duration::try_days(x.into()).expect("TimeDelta::days out of bounds")) .unwrap(); } let from = SqlType::from(v); @@ -614,19 +614,19 @@ mod test { }; fn test_into_t(v: Value, x: &T) - where - Value: Into, - T: PartialEq + fmt::Debug, + where + Value: Into, + T: PartialEq + fmt::Debug, { let a: T = v.into(); assert_eq!(a, *x); } fn test_from_rnd() - where - Value: Into + From, - T: PartialEq + fmt::Debug + Clone, - Standard: Distribution, + where + Value: Into + From, + T: PartialEq + fmt::Debug + Clone, + Standard: Distribution, { for _ in 0..100 { let value = random::(); @@ -635,9 +635,9 @@ mod test { } fn test_from_t(value: &T) - where - Value: Into + From, - T: PartialEq + fmt::Debug + Clone, + where + Value: Into + From, + T: PartialEq + fmt::Debug + Clone, { test_into_t::(Value::from(value.clone()), value); } @@ -785,7 +785,7 @@ mod test { "{}", Value::Array( SqlType::Int32.into(), - Arc::new(vec![Value::Int32(1), Value::Int32(2), Value::Int32(3)]) + Arc::new(vec![Value::Int32(1), Value::Int32(2), Value::Int32(3)]), ) ) ); diff --git a/src/types/value_ref.rs b/src/types/value_ref.rs index 081c0b20..45e6781e 100644 --- a/src/types/value_ref.rs +++ b/src/types/value_ref.rs @@ -145,13 +145,13 @@ impl<'a> fmt::Display for ValueRef<'a> { ValueRef::Float64(v) => fmt::Display::fmt(v, f), ValueRef::Date(v) if f.alternate() => { let date = NaiveDate::from_ymd_opt(1970, 1, 1) - .map(|unix_epoch| unix_epoch + Duration::days((*v).into())) + .map(|unix_epoch| unix_epoch + Duration::try_days((*v).into()).expect("TimeDelta::days out of bounds")) .unwrap(); fmt::Display::fmt(&date, f) } ValueRef::Date(v) => { let date = NaiveDate::from_ymd_opt(1970, 1, 1) - .map(|unix_epoch| unix_epoch + Duration::days((*v).into())) + .map(|unix_epoch| unix_epoch + Duration::try_days((*v).into()).expect("TimeDelta::days out of bounds")) .unwrap(); fmt::Display::fmt(&date.format("%Y-%m-%d"), f) } @@ -428,7 +428,7 @@ macro_rules! value_from { impl<'a> From> for $t { fn from(value: ValueRef<'a>) -> Self { if let ValueRef::$k(v) = value { - return v + return v; } let from = format!("{}", SqlType::from(value.clone())); panic!("Can't convert ValueRef::{} into {}.", @@ -443,7 +443,7 @@ impl<'a> From> for AppDate { fn from(value: ValueRef<'a>) -> Self { if let ValueRef::Date(v) = value { return NaiveDate::from_ymd_opt(1970, 1, 1) - .map(|unix_epoch| unix_epoch + Duration::days(v.into())) + .map(|unix_epoch| unix_epoch + Duration::try_days(v.into()).expect("TimeDelta::days out of bounds")) .unwrap(); } let from = format!("{}", SqlType::from(value.clone())); @@ -560,8 +560,8 @@ mod test { Arc::new(vec![ ValueRef::Int32(1), ValueRef::Int32(2), - ValueRef::Int32(3) - ]) + ValueRef::Int32(3), + ]), ) ) ); @@ -579,7 +579,7 @@ mod test { (format!("{:#}", ValueRef::DateTime(0, *DEFAULT_TZ)) == "Thu, 1 Jan 1970 00:00:00 +0000") || (format!("{:#}", ValueRef::DateTime(0, *DEFAULT_TZ)) - == "Thu, 01 Jan 1970 00:00:00 +0000") + == "Thu, 01 Jan 1970 00:00:00 +0000") ); assert_eq!( @@ -628,12 +628,12 @@ mod test { Arc::new(vec![ ValueRef::Int32(1), ValueRef::Int32(2), - ValueRef::Int32(3) - ]) + ValueRef::Int32(3), + ]), )), Value::Array( SqlType::Int32.into(), - Arc::new(vec![Value::Int32(1), Value::Int32(2), Value::Int32(3)]) + Arc::new(vec![Value::Int32(1), Value::Int32(2), Value::Int32(3)]), ) ) } @@ -684,8 +684,8 @@ mod test { Arc::new(vec![ ValueRef::Int32(1), ValueRef::Int32(2), - ValueRef::Int32(3) - ]) + ValueRef::Int32(3), + ]), )), SqlType::Array(SqlType::Int32.into()) );