-
Notifications
You must be signed in to change notification settings - Fork 169
feat!(insert): RowBinaryWithNamesAndTypes
#244
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
cf3b531
197ce8b
1b24497
dcca54a
e8b9783
ab6047e
901659e
8d76d18
8ecfbe7
ae9a4b1
a0dbeb9
2284cce
d9c3125
36350ac
59be19f
ac2ba48
3be06b0
b383bee
c5699bd
170a545
300de26
fc97158
664b440
08de9a4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,24 +1,26 @@ | ||
| use std::{future::Future, marker::PhantomData, mem, panic, pin::Pin, time::Duration}; | ||
|
|
||
| use crate::headers::{with_authentication, with_request_headers}; | ||
| use crate::row_metadata::RowMetadata; | ||
| use crate::rowbinary::{serialize_row_binary, serialize_with_validation}; | ||
| use crate::{ | ||
| error::{Error, Result}, | ||
| request_body::{ChunkSender, RequestBody}, | ||
| response::Response, | ||
| row::{self, Row}, | ||
| Client, Compression, | ||
| }; | ||
| use bytes::{Bytes, BytesMut}; | ||
| use clickhouse_types::put_rbwnat_columns_header; | ||
| use hyper::{self, Request}; | ||
| use replace_with::replace_with_or_abort; | ||
| use serde::Serialize; | ||
| use std::sync::Arc; | ||
| use std::{future::Future, marker::PhantomData, mem, panic, pin::Pin, time::Duration}; | ||
| use tokio::{ | ||
| task::JoinHandle, | ||
| time::{Instant, Sleep}, | ||
| }; | ||
| use url::Url; | ||
|
|
||
| use crate::headers::{with_authentication, with_request_headers}; | ||
| use crate::{ | ||
| error::{Error, Result}, | ||
| request_body::{ChunkSender, RequestBody}, | ||
| response::Response, | ||
| row::{self, Row}, | ||
| rowbinary, Client, Compression, | ||
| }; | ||
|
|
||
| // The desired max frame size. | ||
| const BUFFER_SIZE: usize = 256 * 1024; | ||
| // Threshold to send a chunk. Should be slightly less than `BUFFER_SIZE` | ||
|
|
@@ -37,6 +39,7 @@ const_assert!(BUFFER_SIZE.is_power_of_two()); // to use the whole buffer's capac | |
| pub struct Insert<T> { | ||
| state: InsertState, | ||
| buffer: BytesMut, | ||
| row_metadata: Option<Arc<RowMetadata>>, | ||
| #[cfg(feature = "lz4")] | ||
| compression: Compression, | ||
| send_timeout: Option<Duration>, | ||
|
|
@@ -119,8 +122,7 @@ macro_rules! timeout { | |
| } | ||
|
|
||
| impl<T> Insert<T> { | ||
| // TODO: remove Result | ||
| pub(crate) fn new(client: &Client, table: &str) -> Result<Self> | ||
| pub(crate) fn new(client: &Client, table: &str, row_metadata: Option<Arc<RowMetadata>>) -> Self | ||
| where | ||
| T: Row, | ||
| { | ||
|
|
@@ -129,9 +131,14 @@ impl<T> Insert<T> { | |
|
|
||
| // TODO: what about escaping a table name? | ||
| // https://clickhouse.com/docs/en/sql-reference/syntax#identifiers | ||
| let sql = format!("INSERT INTO {}({}) FORMAT RowBinary", table, fields); | ||
| let format = if row_metadata.is_some() { | ||
| "RowBinaryWithNamesAndTypes" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd like to see the format types defined as an
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently, this is internal only; we can add some popular formats as an enum provided by the crate. This might be useful with So perhaps it can be introduced in the scope of #174 indeed. However: it should implement |
||
| } else { | ||
| "RowBinary" | ||
| }; | ||
| let sql = format!("INSERT INTO {table}({fields}) FORMAT {format}"); | ||
|
|
||
| Ok(Self { | ||
| Self { | ||
| state: InsertState::NotStarted { | ||
| client: Box::new(client.clone()), | ||
| sql, | ||
|
|
@@ -143,7 +150,8 @@ impl<T> Insert<T> { | |
| end_timeout: None, | ||
| sleep: Box::pin(tokio::time::sleep(Duration::new(0, 0))), | ||
| _marker: PhantomData, | ||
| }) | ||
| row_metadata, | ||
| } | ||
| } | ||
|
|
||
| /// Sets timeouts for different operations. | ||
|
|
@@ -194,6 +202,7 @@ impl<T> Insert<T> { | |
| /// socket. | ||
| /// | ||
| /// Close to: | ||
| /// | ||
| /// ```ignore | ||
| /// async fn write<T>(&self, row: &T) -> Result<usize>; | ||
| /// ``` | ||
|
|
@@ -205,10 +214,11 @@ impl<T> Insert<T> { | |
| /// used anymore. | ||
| /// | ||
| /// # Panics | ||
| /// | ||
| /// If called after the previous call that returned an error. | ||
| pub fn write<'a>(&'a mut self, row: &T) -> impl Future<Output = Result<()>> + 'a + Send | ||
| where | ||
| T: Serialize, | ||
| T: Serialize + Row, | ||
| { | ||
| let result = self.do_write(row); | ||
|
|
||
|
|
@@ -224,7 +234,7 @@ impl<T> Insert<T> { | |
| #[inline(always)] | ||
| pub(crate) fn do_write(&mut self, row: &T) -> Result<usize> | ||
| where | ||
| T: Serialize, | ||
| T: Serialize + Row, | ||
| { | ||
| match self.state { | ||
| InsertState::NotStarted { .. } => self.init_request(), | ||
|
|
@@ -233,7 +243,10 @@ impl<T> Insert<T> { | |
| }?; | ||
|
|
||
| let old_buf_size = self.buffer.len(); | ||
| let result = rowbinary::serialize_into(&mut self.buffer, row); | ||
| let result = match &self.row_metadata { | ||
| Some(metadata) => serialize_with_validation(&mut self.buffer, row, metadata), | ||
| None => serialize_row_binary(&mut self.buffer, row), | ||
| }; | ||
| let written = self.buffer.len() - old_buf_size; | ||
|
|
||
| if result.is_err() { | ||
|
|
@@ -249,7 +262,7 @@ impl<T> Insert<T> { | |
| /// successfully, including all materialized views and quorum writes. | ||
| /// | ||
| /// NOTE: If it isn't called, the whole `INSERT` is aborted. | ||
| pub async fn end(mut self) -> Result<()> { | ||
| pub async fn end(&mut self) -> Result<()> { | ||
|
slvrtrn marked this conversation as resolved.
Outdated
|
||
| if !self.buffer.is_empty() { | ||
| self.send_chunk().await?; | ||
| } | ||
|
|
@@ -264,7 +277,6 @@ impl<T> Insert<T> { | |
| // It's difficult to determine when allocations occur. | ||
| // So, instead we control it manually here and rely on the system allocator. | ||
| let chunk = self.take_and_prepare_chunk()?; | ||
|
|
||
| let sender = self.state.sender().unwrap(); // checked above | ||
|
|
||
| let is_timed_out = match timeout!(self, send_timeout, sender.send(chunk)) { | ||
|
|
@@ -366,6 +378,11 @@ impl<T> Insert<T> { | |
| let handle = | ||
| tokio::spawn(async move { Response::new(future, Compression::None).finish().await }); | ||
|
|
||
| put_rbwnat_columns_header( | ||
| &self.row_metadata.as_ref().unwrap().columns, | ||
| &mut self.buffer, | ||
| )?; | ||
|
|
||
| self.state = InsertState::Active { handle, sender }; | ||
| Ok(()) | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using raw string literals is also something idiomatic to SQLx, though strictly speaking this is only necessary for when you need to quote identifiers as it avoids the need for escapes: https://docs.rs/sqlx/latest/sqlx/macro.query.html#type-overrides-output-columns
Since it appears ClickHouse uses backticks instead of double quotes for quoted identifiers, there's not much benefit to using raw string literals here.