1use std::sync::Arc;
16
17use api::helper::to_pb_time_unit;
18use api::v1::region::region_request::Body as RegionRequestBody;
19use api::v1::region::{
20 BuildIndexRequest, CompactRequest, CompactionTimeRange, FlushRequest, RegionRequestHeader,
21};
22use catalog::CatalogManagerRef;
23use common_catalog::build_db_string;
24use common_meta::node_manager::{AffectedRows, NodeManagerRef};
25use common_meta::peer::Peer;
26use common_telemetry::tracing_context::TracingContext;
27use common_telemetry::{debug, error, info};
28use common_time::range::TimestampRange;
29use common_time::timestamp::TimeUnit as TimestampUnit;
30use futures_util::future;
31use partition::cache::PhysicalPartitionInfo;
32use partition::manager::PartitionRuleManagerRef;
33use session::context::QueryContextRef;
34use snafu::prelude::*;
35use store_api::storage::RegionId;
36use table::requests::{BuildIndexTableRequest, CompactTableRequest, FlushTableRequest};
37
38use crate::error::{
39 CatalogSnafu, FindRegionLeaderSnafu, FindTablePartitionRuleSnafu, JoinTaskSnafu,
40 RequestRegionSnafu, Result, TableNotFoundSnafu, UnsupportedRegionRequestSnafu,
41};
42use crate::region_req_factory::RegionRequestFactory;
43
44pub struct Requester {
46 catalog_manager: CatalogManagerRef,
47 partition_manager: PartitionRuleManagerRef,
48 node_manager: NodeManagerRef,
49}
50
51pub type RequesterRef = Arc<Requester>;
52
53impl Requester {
54 pub fn new(
55 catalog_manager: CatalogManagerRef,
56 partition_manager: PartitionRuleManagerRef,
57 node_manager: NodeManagerRef,
58 ) -> Self {
59 Self {
60 catalog_manager,
61 partition_manager,
62 node_manager,
63 }
64 }
65
66 pub async fn handle_table_flush(
68 &self,
69 request: FlushTableRequest,
70 ctx: QueryContextRef,
71 ) -> Result<AffectedRows> {
72 let partitions = &self
73 .get_table_partition_info(
74 &request.catalog_name,
75 &request.schema_name,
76 &request.table_name,
77 )
78 .await?
79 .partitions;
80
81 let requests = partitions
82 .iter()
83 .map(|partition| {
84 RegionRequestBody::Flush(FlushRequest {
85 region_id: partition.id.into(),
86 })
87 })
88 .collect();
89
90 info!("Handle table manual flush request: {:?}", request);
91
92 self.do_request(
93 requests,
94 Some(build_db_string(&request.catalog_name, &request.schema_name)),
95 &ctx,
96 )
97 .await
98 }
99
100 pub async fn handle_table_build_index(
102 &self,
103 request: BuildIndexTableRequest,
104 ctx: QueryContextRef,
105 ) -> Result<AffectedRows> {
106 let partitions = &self
107 .get_table_partition_info(
108 &request.catalog_name,
109 &request.schema_name,
110 &request.table_name,
111 )
112 .await?
113 .partitions;
114
115 let requests = partitions
116 .iter()
117 .map(|partition| {
118 RegionRequestBody::BuildIndex(BuildIndexRequest {
119 region_id: partition.id.into(),
120 })
121 })
122 .collect();
123
124 info!(
125 "Handle table manual build index for table {}",
126 request.table_name
127 );
128 debug!("Request details: {:?}", request);
129
130 self.do_request(
131 requests,
132 Some(build_db_string(&request.catalog_name, &request.schema_name)),
133 &ctx,
134 )
135 .await
136 }
137
138 pub async fn handle_table_compaction(
140 &self,
141 request: CompactTableRequest,
142 ctx: QueryContextRef,
143 ) -> Result<AffectedRows> {
144 let partitions = &self
145 .get_table_partition_info(
146 &request.catalog_name,
147 &request.schema_name,
148 &request.table_name,
149 )
150 .await?
151 .partitions;
152
153 let time_range = request
154 .time_range
155 .map(to_pb_compaction_time_range)
156 .transpose()?;
157 let requests = partitions
158 .iter()
159 .map(|partition| {
160 RegionRequestBody::Compact(CompactRequest {
161 region_id: partition.id.into(),
162 parallelism: request.parallelism,
163 options: Some(request.compact_options),
164 time_range,
165 })
166 })
167 .collect();
168
169 info!("Handle table manual compaction request: {:?}", request);
170
171 self.do_request(
172 requests,
173 Some(build_db_string(&request.catalog_name, &request.schema_name)),
174 &ctx,
175 )
176 .await
177 }
178
179 pub async fn handle_region_flush(
181 &self,
182 region_id: RegionId,
183 ctx: QueryContextRef,
184 ) -> Result<AffectedRows> {
185 let request = RegionRequestBody::Flush(FlushRequest {
186 region_id: region_id.into(),
187 });
188
189 info!("Handle region manual flush request: {region_id}");
190 self.do_request(vec![request], None, &ctx).await
191 }
192
193 pub async fn handle_region_compaction(
195 &self,
196 region_id: RegionId,
197 ctx: QueryContextRef,
198 ) -> Result<AffectedRows> {
199 let request = RegionRequestBody::Compact(CompactRequest {
200 region_id: region_id.into(),
201 parallelism: 1,
202 options: None, time_range: None,
204 });
205
206 info!("Handle region manual compaction request: {region_id}");
207 self.do_request(vec![request], None, &ctx).await
208 }
209}
210
211fn to_pb_compaction_time_range(range: TimestampRange) -> Result<CompactionTimeRange> {
212 let (Some(start), Some(end)) = (*range.start(), *range.end()) else {
213 return crate::error::InvalidTimestampRangeSnafu {
214 start: format!("{:?}", range.start()),
215 end: format!("{:?}", range.end()),
216 }
217 .fail();
218 };
219 let original_start = start;
220 let original_end = end;
221 let start = start.convert_to(TimestampUnit::Second).with_context(|| {
222 crate::error::InvalidTimestampRangeSnafu {
223 start: format!("{original_start:?}"),
224 end: format!("{original_end:?}"),
225 }
226 })?;
227 let end = end
228 .convert_to_ceil(TimestampUnit::Second)
229 .with_context(|| crate::error::InvalidTimestampRangeSnafu {
230 start: format!("{original_start:?}"),
231 end: format!("{original_end:?}"),
232 })?;
233 ensure!(
234 start < end,
235 crate::error::InvalidTimestampRangeSnafu {
236 start: format!("{original_start:?}"),
237 end: format!("{original_end:?}"),
238 }
239 );
240
241 Ok(CompactionTimeRange {
242 start: start.value(),
243 end: end.value(),
244 time_unit: to_pb_time_unit(TimestampUnit::Second) as i32,
245 })
246}
247
248impl Requester {
249 async fn do_request(
250 &self,
251 requests: Vec<RegionRequestBody>,
252 db_string: Option<String>,
253 ctx: &QueryContextRef,
254 ) -> Result<AffectedRows> {
255 let request_factory = RegionRequestFactory::new(RegionRequestHeader {
256 tracing_context: TracingContext::from_current_span().to_w3c(),
257 dbname: db_string.unwrap_or_else(|| ctx.get_db_string()),
258 ..Default::default()
259 });
260
261 let tasks = requests.into_iter().map(|req_body| {
262 let request = request_factory.build_request(req_body.clone());
263 let partition_manager = self.partition_manager.clone();
264 let node_manager = self.node_manager.clone();
265 common_runtime::spawn_global(async move {
266 let peer =
267 Self::find_region_leader_by_request(partition_manager, &req_body).await?;
268 node_manager
269 .datanode(&peer)
270 .await
271 .handle(request)
272 .await
273 .context(RequestRegionSnafu)
274 })
275 });
276 let results = future::try_join_all(tasks).await.context(JoinTaskSnafu)?;
277
278 let affected_rows = results
279 .into_iter()
280 .map(|resp| resp.map(|r| r.affected_rows))
281 .sum::<Result<AffectedRows>>()?;
282
283 Ok(affected_rows)
284 }
285
286 async fn find_region_leader_by_request(
287 partition_manager: PartitionRuleManagerRef,
288 req: &RegionRequestBody,
289 ) -> Result<Peer> {
290 let region_id = match req {
291 RegionRequestBody::Flush(req) => req.region_id,
292 RegionRequestBody::Compact(req) => req.region_id,
293 RegionRequestBody::BuildIndex(req) => req.region_id,
294 _ => {
295 error!("Unsupported region request: {:?}", req);
296 return UnsupportedRegionRequestSnafu {}.fail();
297 }
298 };
299
300 partition_manager
301 .find_region_leader(region_id.into())
302 .await
303 .context(FindRegionLeaderSnafu)
304 }
305
306 async fn get_table_partition_info(
307 &self,
308 catalog: &str,
309 schema: &str,
310 table_name: &str,
311 ) -> Result<Arc<PhysicalPartitionInfo>> {
312 let table = self
313 .catalog_manager
314 .table(catalog, schema, table_name, None)
315 .await
316 .context(CatalogSnafu)?;
317
318 let table = table.with_context(|| TableNotFoundSnafu {
319 table_name: common_catalog::format_full_table_name(catalog, schema, table_name),
320 })?;
321 let table_info = table.table_info();
322
323 self.partition_manager
324 .find_physical_partition_info(table_info.ident.table_id)
325 .await
326 .with_context(|_| FindTablePartitionRuleSnafu {
327 table_name: common_catalog::format_full_table_name(catalog, schema, table_name),
328 })
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use api::v1::TimeUnit;
335 use common_time::Timestamp;
336 use common_time::range::TimestampRange;
337
338 use super::*;
339
340 #[test]
341 fn test_to_pb_compaction_time_range_normalizes_mixed_units() {
342 let range = TimestampRange::new(
343 Timestamp::new_millisecond(1_500),
344 Timestamp::new_microsecond(2_500_000),
345 )
346 .unwrap();
347
348 let pb_range = to_pb_compaction_time_range(range).unwrap();
349 assert_eq!(1, pb_range.start);
350 assert_eq!(3, pb_range.end);
351 assert_eq!(TimeUnit::Second as i32, pb_range.time_unit);
352 }
353
354 #[test]
355 fn test_to_pb_compaction_time_range() {
356 let range = TimestampRange::new(
357 Timestamp::new_microsecond(1_000),
358 Timestamp::new_microsecond(2_000),
359 )
360 .unwrap();
361
362 let pb_range = to_pb_compaction_time_range(range).unwrap();
363 assert_eq!(0, pb_range.start);
364 assert_eq!(1, pb_range.end);
365 assert_eq!(TimeUnit::Second as i32, pb_range.time_unit);
366 }
367}