operator/statement/
copy_table_to.rs1use std::collections::HashMap;
16use std::sync::Arc;
17
18use client::OutputData;
19use common_base::readable_size::ReadableSize;
20use common_datasource::file_format::Format;
21use common_datasource::file_format::csv::stream_to_csv;
22use common_datasource::file_format::json::stream_to_json;
23use common_datasource::file_format::parquet::stream_to_parquet;
24use common_datasource::object_store::build_backend_for_write_with_path;
25use common_query::Output;
26use common_recordbatch::adapter::DfRecordBatchStreamAdapter;
27use common_recordbatch::{
28 SendableRecordBatchMapper, SendableRecordBatchStream, map_json_type_to_string,
29 map_json_type_to_string_schema,
30};
31use common_telemetry::{debug, tracing};
32use datafusion::datasource::DefaultTableSource;
33use datafusion_common::TableReference as DfTableReference;
34use datafusion_expr::LogicalPlanBuilder;
35use object_store::ObjectStore;
36use session::context::QueryContextRef;
37use snafu::{OptionExt, ResultExt};
38use table::TableRef;
39use table::requests::CopyTableRequest;
40use table::table::adapter::DfTableProviderAdapter;
41use table::table_reference::TableReference;
42
43use crate::error::{self, BuildDfLogicalPlanSnafu, ExecLogicalPlanSnafu, Result};
44use crate::statement::StatementExecutor;
45
46const WRITE_BUFFER_THRESHOLD: ReadableSize = ReadableSize::mb(8);
49
50const WRITE_CONCURRENCY: usize = 8;
52
53impl StatementExecutor {
54 async fn stream_to_file(
55 &self,
56 stream: SendableRecordBatchStream,
57 format: &Format,
58 object_store: ObjectStore,
59 path: &str,
60 ) -> Result<usize> {
61 let threshold = WRITE_BUFFER_THRESHOLD.as_bytes() as usize;
62
63 let stream = Box::pin(SendableRecordBatchMapper::new(
64 stream,
65 map_json_type_to_string,
66 map_json_type_to_string_schema,
67 ));
68 match format {
69 Format::Csv(format) => stream_to_csv(
70 Box::pin(DfRecordBatchStreamAdapter::new(stream)),
71 object_store,
72 path,
73 threshold,
74 WRITE_CONCURRENCY,
75 format,
76 )
77 .await
78 .context(error::WriteStreamToFileSnafu { path }),
79 Format::Json(format) => stream_to_json(
80 Box::pin(DfRecordBatchStreamAdapter::new(stream)),
81 object_store,
82 path,
83 threshold,
84 WRITE_CONCURRENCY,
85 format,
86 )
87 .await
88 .context(error::WriteStreamToFileSnafu { path }),
89 Format::Parquet(_) => stream_to_parquet(
90 Box::pin(DfRecordBatchStreamAdapter::new(stream)),
91 object_store,
92 path,
93 WRITE_CONCURRENCY,
94 )
95 .await
96 .context(error::WriteStreamToFileSnafu { path }),
97 _ => error::UnsupportedFormatSnafu {
98 format: format.clone(),
99 }
100 .fail(),
101 }
102 }
103
104 #[tracing::instrument(skip_all)]
105 pub(crate) async fn copy_table_to(
106 &self,
107 req: CopyTableRequest,
108 query_ctx: QueryContextRef,
109 ) -> Result<usize> {
110 let table_ref = TableReference::full(&req.catalog_name, &req.schema_name, &req.table_name);
111 let table = self.get_table(&table_ref).await?;
112 self.copy_captured_table_to(table, req, query_ctx).await
113 }
114
115 pub(crate) async fn copy_captured_table_to(
116 &self,
117 table: TableRef,
118 req: CopyTableRequest,
119 query_ctx: QueryContextRef,
120 ) -> Result<usize> {
121 let info = table.table_info();
122 let table_ref = TableReference::full(&info.catalog_name, &info.schema_name, &info.name);
123 let table_id = info.table_id();
124 let format = Format::try_from(&req.with).context(error::ParseFileFormatSnafu)?;
125
126 let df_table_ref = DfTableReference::from(table_ref);
127
128 let filters = table
129 .schema()
130 .timestamp_column()
131 .and_then(|c| {
132 common_query::logical_plan::build_filter_from_timestamp(
133 &c.name,
134 req.timestamp_range.as_ref(),
135 )
136 })
137 .into_iter()
138 .collect::<Vec<_>>();
139
140 let table_provider = Arc::new(DfTableProviderAdapter::new(table));
141 let table_source = Arc::new(DefaultTableSource::new(table_provider));
142
143 let mut builder = LogicalPlanBuilder::scan_with_filters(
144 df_table_ref,
145 table_source,
146 None,
147 filters.clone(),
148 )
149 .context(BuildDfLogicalPlanSnafu)?;
150 for f in filters {
151 builder = builder.filter(f).context(BuildDfLogicalPlanSnafu)?;
152 }
153 let plan = builder.build().context(BuildDfLogicalPlanSnafu)?;
154
155 let output = self
156 .query_engine
157 .execute(plan, query_ctx)
158 .await
159 .context(ExecLogicalPlanSnafu)?;
160
161 let CopyTableRequest {
162 location,
163 connection,
164 ..
165 } = &req;
166
167 debug!("Copy table: {table_id} to location: {location}");
168 self.copy_to_file(&format, output, location, connection)
169 .await
170 }
171
172 pub(crate) async fn copy_to_file(
173 &self,
174 format: &Format,
175 output: Output,
176 location: &str,
177 connection: &HashMap<String, String>,
178 ) -> Result<usize> {
179 let output = output
180 .map_dictionary_to_values()
181 .context(error::BuildRecordBatchSnafu)?;
182 let stream = match output.data {
183 OutputData::Stream(stream) => stream,
184 OutputData::RecordBatches(record_batches) => record_batches.as_stream(),
185 _ => unreachable!(),
186 };
187
188 let backend =
189 build_backend_for_write_with_path(location, connection, &self.local_file_access)
190 .await
191 .context(error::BuildBackendSnafu)?;
192 let filename = backend.object_path.context(error::UnexpectedSnafu {
193 violated: format!("Expected filename, path: {location}"),
194 })?;
195 self.stream_to_file(stream, format, backend.object_store, &filename)
196 .await
197 }
198}