google_cloud_bigquery/write/
client.rs1use super::client_builder::ClientBuilder;
16use super::pool::{StreamPool, StreamPoolOptions};
17use super::retry_policy::RetryOptions;
18use super::stream_type::{ApplicationCreatedStream, DefaultStream};
19use super::transport::Transport;
20use super::writer_builder::WriterBuilder;
21use crate::ClientBuilderResult as BuilderResult;
22use std::collections::HashMap;
23use std::sync::{Arc, Mutex};
24
25#[derive(Debug)]
27pub struct Write {
28 inner: Arc<Transport>,
29 pools: Arc<Mutex<HashMap<String, Arc<StreamPool>>>>,
30 pool_options: StreamPoolOptions,
31 retry_options: RetryOptions,
32}
33
34impl Write {
35 pub fn builder() -> ClientBuilder {
37 ClientBuilder::new()
38 }
39
40 pub(crate) async fn new(builder: ClientBuilder) -> BuilderResult<Self> {
41 let inner = Arc::new(Transport::new(builder.config).await?);
42 let pools = Arc::new(Mutex::new(HashMap::new()));
43 Ok(Self {
44 inner,
45 pools,
46 pool_options: builder.pool_options,
47 retry_options: builder.retry_options,
48 })
49 }
50
51 pub fn open_default_stream<T: Into<String>>(&self, table: T) -> WriterBuilder<DefaultStream> {
71 WriterBuilder::new_open_default(
72 self.inner.clone(),
73 self.pools.clone(),
74 self.pool_options.clone(),
75 self.retry_options.clone(),
76 table.into(),
77 )
78 }
79
80 pub fn create_stream<S: ApplicationCreatedStream, T: Into<String>>(
109 &self,
110 table: T,
111 ) -> WriterBuilder<S> {
112 WriterBuilder::new_create(self.inner.clone(), self.retry_options.clone(), table.into())
113 }
114
115 pub fn attach_to_stream<S: ApplicationCreatedStream, T: Into<String>>(
145 &self,
146 write_stream: T,
147 ) -> WriterBuilder<S> {
148 WriterBuilder::new_attach(
149 self.inner.clone(),
150 self.retry_options.clone(),
151 write_stream.into(),
152 )
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::super::error::AppendError;
159 use super::*;
160 use crate::model::{ArrowRecordBatch, ArrowSchema, ProtoRows, ProtoSchema};
161 use bigquery_grpc_mock::{MockBigQueryWrite, start};
162 use gaxi::grpc::tonic::Status as TonicStatus;
163 use google_cloud_auth::credentials::anonymous::Builder as Anonymous;
164
165 #[tokio::test]
166 async fn arrow() -> anyhow::Result<()> {
167 let mut mock = MockBigQueryWrite::new();
168 mock.expect_append_rows()
169 .return_once(|_| Err(TonicStatus::failed_precondition("fail")));
170 let (endpoint, _server) = start("0.0.0.0:0", mock).await?;
171 let client = Write::builder()
172 .with_endpoint(endpoint)
173 .with_credentials(Anonymous::new().build())
174 .build()
175 .await?;
176 let writer = client
177 .open_default_stream("projects/p/datasets/d/tables/t")
178 .build_arrow(ArrowSchema::new())
179 .await?;
180 let err = writer
181 .append(ArrowRecordBatch::new())
182 .send()
183 .await
184 .expect_err("write should fail");
185 assert!(matches!(err, AppendError::Rpc { source: _ }));
186
187 Ok(())
188 }
189
190 #[tokio::test]
191 async fn proto() -> anyhow::Result<()> {
192 let mut mock = MockBigQueryWrite::new();
193 mock.expect_append_rows()
194 .return_once(|_| Err(TonicStatus::failed_precondition("fail")));
195 let (endpoint, _server) = start("0.0.0.0:0", mock).await?;
196 let client = Write::builder()
197 .with_endpoint(endpoint)
198 .with_credentials(Anonymous::new().build())
199 .build()
200 .await?;
201 let writer = client
202 .open_default_stream("projects/p/datasets/d/tables/t")
203 .build_proto(ProtoSchema::new())
204 .await?;
205 let err = writer
206 .append(ProtoRows::new())
207 .send()
208 .await
209 .expect_err("write should fail");
210 assert!(matches!(err, AppendError::Rpc { source: _ }));
211
212 Ok(())
213 }
214
215 #[tokio::test]
216 async fn multiplexing() -> anyhow::Result<()> {
217 let mut mock = MockBigQueryWrite::new();
218 mock.expect_get_write_stream().times(2).returning(|req| {
219 let name = req.into_inner().name;
220 Ok(gaxi::grpc::tonic::Response::new(
221 bigquery_grpc_mock::google::cloud::bigquery::storage::v1::WriteStream {
222 name,
223 location: "us".to_string(),
224 ..Default::default()
225 },
226 ))
227 });
228 let (endpoint, _server) = start("0.0.0.0:0", mock).await?;
229 let client = Write::builder()
230 .with_endpoint(endpoint)
231 .with_credentials(Anonymous::new().build())
232 .build()
233 .await?;
234 let multiplexed_writer1 = client
235 .open_default_stream("projects/p/datasets/d/tables/t1")
236 .with_multiplexing(true)
237 .build_arrow(ArrowSchema::new())
238 .await?;
239 let multiplexed_writer2 = client
240 .open_default_stream("projects/p/datasets/d/tables/t2")
241 .with_multiplexing(true)
242 .build_arrow(ArrowSchema::new())
243 .await?;
244 assert!(Arc::ptr_eq(
245 &multiplexed_writer1.inner.pool,
246 &multiplexed_writer2.inner.pool
247 ));
248
249 let standalone_writer = client
250 .open_default_stream("projects/p/datasets/d/tables/t3")
251 .with_multiplexing(false)
252 .build_arrow(ArrowSchema::new())
253 .await?;
254 assert!(!Arc::ptr_eq(
255 &multiplexed_writer1.inner.pool,
256 &standalone_writer.inner.pool
257 ));
258
259 Ok(())
260 }
261
262 #[tokio::test]
263 async fn format_isolation() -> anyhow::Result<()> {
264 let mut mock = MockBigQueryWrite::new();
265 mock.expect_get_write_stream().times(2).returning(|req| {
266 let name = req.into_inner().name;
267 Ok(gaxi::grpc::tonic::Response::new(
268 bigquery_grpc_mock::google::cloud::bigquery::storage::v1::WriteStream {
269 name,
270 location: "us".to_string(),
271 ..Default::default()
272 },
273 ))
274 });
275 let (endpoint, _server) = start("0.0.0.0:0", mock).await?;
276 let client = Write::builder()
277 .with_endpoint(endpoint)
278 .with_credentials(Anonymous::new().build())
279 .build()
280 .await?;
281 let arrow_writer = client
282 .open_default_stream("projects/p/datasets/d/tables/t1")
283 .with_multiplexing(true)
284 .build_arrow(ArrowSchema::new())
285 .await?;
286 let proto_writer = client
287 .open_default_stream("projects/p/datasets/d/tables/t2")
288 .with_multiplexing(true)
289 .build_proto(ProtoSchema::new())
290 .await?;
291
292 assert!(!Arc::ptr_eq(
294 &arrow_writer.inner.pool,
295 &proto_writer.inner.pool
296 ));
297
298 Ok(())
299 }
300
301 #[tokio::test]
302 async fn location_isolation() -> anyhow::Result<()> {
303 let mut mock = MockBigQueryWrite::new();
304 mock.expect_get_write_stream().times(3).returning(|req| {
305 let name = req.into_inner().name;
306 let location = if name.contains("t1") {
307 "us".to_string()
308 } else if name.contains("t2") {
309 "eu".to_string()
310 } else {
311 "us".to_string()
312 };
313 Ok(gaxi::grpc::tonic::Response::new(
314 bigquery_grpc_mock::google::cloud::bigquery::storage::v1::WriteStream {
315 name,
316 location,
317 ..Default::default()
318 },
319 ))
320 });
321 let (endpoint, _server) = start("0.0.0.0:0", mock).await?;
322 let client = Write::builder()
323 .with_endpoint(endpoint)
324 .with_credentials(Anonymous::new().build())
325 .build()
326 .await?;
327 let us_writer1 = client
328 .open_default_stream("projects/p/datasets/d/tables/t1")
329 .with_multiplexing(true)
330 .build_arrow(ArrowSchema::new())
331 .await?;
332 let eu_writer = client
333 .open_default_stream("projects/p/datasets/d/tables/t2")
334 .with_multiplexing(true)
335 .build_arrow(ArrowSchema::new())
336 .await?;
337
338 assert!(!Arc::ptr_eq(&us_writer1.inner.pool, &eu_writer.inner.pool));
340
341 let us_writer2 = client
342 .open_default_stream("projects/p/datasets/d/tables/t3")
343 .with_multiplexing(true)
344 .build_arrow(ArrowSchema::new())
345 .await?;
346
347 assert!(Arc::ptr_eq(&us_writer1.inner.pool, &us_writer2.inner.pool));
349
350 Ok(())
351 }
352
353 #[tokio::test]
354 async fn retry_options() -> anyhow::Result<()> {
355 let client = Write::builder()
356 .with_credentials(Anonymous::new().build())
357 .build()
358 .await?;
359 let writer = client
360 .open_default_stream("projects/p/datasets/d/tables/t")
361 .build_arrow(ArrowSchema::new())
362 .await?;
363
364 let options = &writer.inner.options;
366 assert!(Arc::ptr_eq(
367 &client.retry_options.retry_policy,
368 &options.retry_policy
369 ));
370 assert!(Arc::ptr_eq(
371 &client.retry_options.backoff_policy,
372 &options.backoff_policy
373 ));
374 assert_eq!(
375 client.retry_options.attempt_timeout,
376 options.attempt_timeout
377 );
378
379 Ok(())
380 }
381}