1use core::pin::pin;
16use std::sync::{Arc, Weak};
17
18use arrow_schema::SchemaRef as ArrowSchemaRef;
19use common_catalog::consts::INFORMATION_SCHEMA_REGION_PEERS_TABLE_ID;
20use common_error::ext::BoxedError;
21use common_meta::rpc::router::RegionRoute;
22use common_recordbatch::adapter::RecordBatchStreamAdapter;
23use common_recordbatch::{RecordBatch, SendableRecordBatchStream};
24use datafusion::common::HashMap;
25use datafusion::execution::TaskContext;
26use datafusion::physical_plan::SendableRecordBatchStream as DfSendableRecordBatchStream;
27use datafusion::physical_plan::stream::RecordBatchStreamAdapter as DfRecordBatchStreamAdapter;
28use datafusion::physical_plan::streaming::PartitionStream as DfPartitionStream;
29use datatypes::prelude::{ConcreteDataType, ScalarVectorBuilder, VectorRef};
30use datatypes::schema::{ColumnSchema, Schema, SchemaRef};
31use datatypes::value::Value;
32use datatypes::vectors::{Int64VectorBuilder, StringVectorBuilder, UInt64VectorBuilder};
33use futures::{StreamExt, TryStreamExt};
34use snafu::{OptionExt, ResultExt};
35use store_api::storage::{RegionId, ScanRequest, TableId};
36use table::metadata::TableType;
37
38use crate::CatalogManager;
39use crate::error::{
40 CreateRecordBatchSnafu, FindRegionRoutesSnafu, InternalSnafu, Result,
41 UpgradeWeakCatalogManagerRefSnafu,
42};
43use crate::kvbackend::KvBackendCatalogManager;
44use crate::system_schema::SystemTable;
45use crate::system_schema::information_schema::{
46 InformationSchemaTableScope, InformationTable, Predicates, REGION_PEERS,
47};
48
49pub const TABLE_CATALOG: &str = "table_catalog";
50pub const TABLE_SCHEMA: &str = "table_schema";
51pub const TABLE_NAME: &str = "table_name";
52pub const REGION_ID: &str = "region_id";
53pub const PEER_ID: &str = "peer_id";
54const PEER_ADDR: &str = "peer_addr";
55pub const IS_LEADER: &str = "is_leader";
56const STATUS: &str = "status";
57const DOWN_SECONDS: &str = "down_seconds";
58const INIT_CAPACITY: usize = 42;
59
60#[derive(Debug)]
72pub(super) struct InformationSchemaRegionPeers {
73 schema: SchemaRef,
74 catalog_name: String,
75 catalog_manager: Weak<dyn CatalogManager>,
76}
77
78impl InformationSchemaRegionPeers {
79 pub(super) fn new(catalog_name: String, catalog_manager: Weak<dyn CatalogManager>) -> Self {
80 Self {
81 schema: Self::schema(),
82 catalog_name,
83 catalog_manager,
84 }
85 }
86
87 pub(crate) fn schema() -> SchemaRef {
88 Arc::new(Schema::new(vec![
89 ColumnSchema::new(TABLE_CATALOG, ConcreteDataType::string_datatype(), false),
90 ColumnSchema::new(TABLE_SCHEMA, ConcreteDataType::string_datatype(), false),
91 ColumnSchema::new(TABLE_NAME, ConcreteDataType::string_datatype(), false),
92 ColumnSchema::new(REGION_ID, ConcreteDataType::uint64_datatype(), false),
93 ColumnSchema::new(PEER_ID, ConcreteDataType::uint64_datatype(), true),
94 ColumnSchema::new(PEER_ADDR, ConcreteDataType::string_datatype(), true),
95 ColumnSchema::new(IS_LEADER, ConcreteDataType::string_datatype(), true),
96 ColumnSchema::new(STATUS, ConcreteDataType::string_datatype(), true),
97 ColumnSchema::new(DOWN_SECONDS, ConcreteDataType::int64_datatype(), true),
98 ]))
99 }
100
101 fn builder(&self) -> InformationSchemaRegionPeersBuilder {
102 InformationSchemaRegionPeersBuilder::new(
103 self.schema.clone(),
104 self.catalog_name.clone(),
105 self.catalog_manager.clone(),
106 )
107 }
108}
109
110impl InformationTable for InformationSchemaRegionPeers {
111 fn scope(&self) -> InformationSchemaTableScope {
112 InformationSchemaTableScope::Catalog
113 }
114}
115
116impl SystemTable for InformationSchemaRegionPeers {
117 fn table_id(&self) -> TableId {
118 INFORMATION_SCHEMA_REGION_PEERS_TABLE_ID
119 }
120
121 fn table_name(&self) -> &'static str {
122 REGION_PEERS
123 }
124
125 fn schema(&self) -> SchemaRef {
126 self.schema.clone()
127 }
128
129 fn to_stream(&self, request: ScanRequest) -> Result<SendableRecordBatchStream> {
130 let schema = self.schema.arrow_schema().clone();
131 let mut builder = self.builder();
132 let stream = Box::pin(DfRecordBatchStreamAdapter::new(
133 schema,
134 futures::stream::once(async move {
135 builder
136 .make_region_peers(Some(request))
137 .await
138 .map(|x| x.into_df_record_batch())
139 .map_err(Into::into)
140 }),
141 ));
142 Ok(Box::pin(
143 RecordBatchStreamAdapter::try_new(stream)
144 .map_err(BoxedError::new)
145 .context(InternalSnafu)?,
146 ))
147 }
148}
149
150struct InformationSchemaRegionPeersBuilder {
151 schema: SchemaRef,
152 catalog_name: String,
153 catalog_manager: Weak<dyn CatalogManager>,
154
155 table_catalogs: StringVectorBuilder,
156 table_schemas: StringVectorBuilder,
157 table_names: StringVectorBuilder,
158 region_ids: UInt64VectorBuilder,
159 peer_ids: UInt64VectorBuilder,
160 peer_addrs: StringVectorBuilder,
161 is_leaders: StringVectorBuilder,
162 statuses: StringVectorBuilder,
163 down_seconds: Int64VectorBuilder,
164}
165
166impl InformationSchemaRegionPeersBuilder {
167 fn new(
168 schema: SchemaRef,
169 catalog_name: String,
170 catalog_manager: Weak<dyn CatalogManager>,
171 ) -> Self {
172 Self {
173 schema,
174 catalog_name,
175 catalog_manager,
176 table_catalogs: StringVectorBuilder::with_capacity(INIT_CAPACITY),
177 table_schemas: StringVectorBuilder::with_capacity(INIT_CAPACITY),
178 table_names: StringVectorBuilder::with_capacity(INIT_CAPACITY),
179 region_ids: UInt64VectorBuilder::with_capacity(INIT_CAPACITY),
180 peer_ids: UInt64VectorBuilder::with_capacity(INIT_CAPACITY),
181 peer_addrs: StringVectorBuilder::with_capacity(INIT_CAPACITY),
182 is_leaders: StringVectorBuilder::with_capacity(INIT_CAPACITY),
183 statuses: StringVectorBuilder::with_capacity(INIT_CAPACITY),
184 down_seconds: Int64VectorBuilder::with_capacity(INIT_CAPACITY),
185 }
186 }
187
188 async fn make_region_peers(&mut self, request: Option<ScanRequest>) -> Result<RecordBatch> {
190 let catalog_name = self.catalog_name.clone();
191 let catalog_manager = self
192 .catalog_manager
193 .upgrade()
194 .context(UpgradeWeakCatalogManagerRefSnafu)?;
195
196 let partition_manager = catalog_manager
197 .as_any()
198 .downcast_ref::<KvBackendCatalogManager>()
199 .map(|catalog_manager| catalog_manager.partition_manager());
200
201 let predicates = Predicates::from_scan_request(&request);
202
203 for schema_name in catalog_manager.schema_names(&catalog_name, None).await? {
204 let table_stream = catalog_manager
205 .tables(&catalog_name, &schema_name, None)
206 .try_filter_map(|t| async move {
207 let table_info = t.table_info();
208 if table_info.table_type == TableType::Temporary {
209 Ok(None)
210 } else {
211 Ok(Some((table_info.ident.table_id, table_info.name.clone())))
212 }
213 });
214
215 const BATCH_SIZE: usize = 128;
216
217 let mut table_chunks = pin!(table_stream.ready_chunks(BATCH_SIZE));
219
220 while let Some(tables) = table_chunks.next().await {
221 let tables = tables.into_iter().collect::<Result<HashMap<_, _>>>()?;
222 let table_ids = tables.keys().cloned().collect::<Vec<_>>();
223
224 let table_routes = if let Some(partition_manager) = &partition_manager {
225 partition_manager
226 .batch_find_region_routes(&table_ids)
227 .await
228 .context(FindRegionRoutesSnafu)?
229 } else {
230 table_ids.into_iter().map(|id| (id, vec![])).collect()
231 };
232
233 for (table_id, routes) in table_routes {
234 let table_name = tables.get(&table_id).unwrap();
236 self.add_region_peers(
237 &catalog_name,
238 &schema_name,
239 table_name,
240 &predicates,
241 table_id,
242 &routes,
243 );
244 }
245 }
246 }
247
248 self.finish()
249 }
250
251 fn add_region_peers(
252 &mut self,
253 table_catalog: &str,
254 table_schema: &str,
255 table_name: &str,
256 predicates: &Predicates,
257 table_id: TableId,
258 routes: &[RegionRoute],
259 ) {
260 for route in routes {
261 let region_id = RegionId::new(table_id, route.region.id.region_number()).as_u64();
262 let peer_id = route.leader_peer.clone().map(|p| p.id);
263 let peer_addr = route.leader_peer.clone().map(|p| p.addr);
264 let state = if let Some(state) = route.leader_state {
265 Some(state.as_ref().to_string())
266 } else {
267 Some("ALIVE".to_string())
269 };
270
271 let row = [
272 (TABLE_CATALOG, &Value::from(table_catalog)),
273 (TABLE_SCHEMA, &Value::from(table_schema)),
274 (TABLE_NAME, &Value::from(table_name)),
275 (REGION_ID, &Value::from(region_id)),
276 ];
277
278 if !predicates.eval(&row) {
279 continue;
280 }
281
282 self.table_catalogs.push(Some(table_catalog));
283 self.table_schemas.push(Some(table_schema));
284 self.table_names.push(Some(table_name));
285 self.region_ids.push(Some(region_id));
286 self.peer_ids.push(peer_id);
287 self.peer_addrs.push(peer_addr.as_deref());
288 self.is_leaders.push(Some("Yes"));
289 self.statuses.push(state.as_deref());
290 self.down_seconds
291 .push(route.leader_down_millis().map(|m| m / 1000));
292
293 for follower in &route.follower_peers {
294 self.table_catalogs.push(Some(table_catalog));
295 self.table_schemas.push(Some(table_schema));
296 self.table_names.push(Some(table_name));
297 self.region_ids.push(Some(region_id));
298 self.peer_ids.push(Some(follower.id));
299 self.peer_addrs.push(Some(follower.addr.as_str()));
300 self.is_leaders.push(Some("No"));
301 self.statuses.push(None);
302 self.down_seconds.push(None);
303 }
304 }
305 }
306
307 fn finish(&mut self) -> Result<RecordBatch> {
308 let columns: Vec<VectorRef> = vec![
309 Arc::new(self.table_catalogs.finish()),
310 Arc::new(self.table_schemas.finish()),
311 Arc::new(self.table_names.finish()),
312 Arc::new(self.region_ids.finish()),
313 Arc::new(self.peer_ids.finish()),
314 Arc::new(self.peer_addrs.finish()),
315 Arc::new(self.is_leaders.finish()),
316 Arc::new(self.statuses.finish()),
317 Arc::new(self.down_seconds.finish()),
318 ];
319 RecordBatch::new(self.schema.clone(), columns).context(CreateRecordBatchSnafu)
320 }
321}
322
323impl DfPartitionStream for InformationSchemaRegionPeers {
324 fn schema(&self) -> &ArrowSchemaRef {
325 self.schema.arrow_schema()
326 }
327
328 fn execute(&self, _: Arc<TaskContext>) -> DfSendableRecordBatchStream {
329 let schema = self.schema.arrow_schema().clone();
330 let mut builder = self.builder();
331 Box::pin(DfRecordBatchStreamAdapter::new(
332 schema,
333 futures::stream::once(async move {
334 builder
335 .make_region_peers(None)
336 .await
337 .map(|x| x.into_df_record_batch())
338 .map_err(Into::into)
339 }),
340 ))
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use api::v1::meta::Peer;
347 use arrow::array::AsArray;
348 use common_meta::rpc::router::{Region, RegionRoute};
349 use datafusion::common::ScalarValue;
350 use datafusion::logical_expr::{BinaryExpr, Expr, Operator, col};
351 use store_api::storage::{RegionId, ScanRequest};
352
353 use super::*;
354
355 fn new_region_route(table_id: u32, region_number: u32, peer_id: u64) -> RegionRoute {
356 RegionRoute {
357 region: Region {
358 id: RegionId::new(table_id, region_number),
359 ..Default::default()
360 },
361 leader_peer: Some(Peer {
362 id: peer_id,
363 addr: format!("127.0.0.1:{}", 3000 + peer_id),
364 }),
365 follower_peers: vec![],
366 leader_state: None,
367 leader_down_since: None,
368 write_route_policy: None,
369 }
370 }
371
372 #[test]
373 fn test_add_region_peers_predicate_filters_correctly() {
374 let schema = InformationSchemaRegionPeers::schema();
375 let mut builder = InformationSchemaRegionPeersBuilder::new(
376 schema,
377 "greptime".to_string(),
378 Weak::<KvBackendCatalogManager>::new(),
379 );
380
381 let table_id = 1;
382 let routes = vec![
384 new_region_route(table_id, 0, 1),
385 new_region_route(table_id, 1, 2),
386 new_region_route(table_id, 2, 3),
387 ];
388
389 let target_region_id = RegionId::new(table_id, 2).as_u64();
394 let filter = Expr::BinaryExpr(BinaryExpr::new(
395 Box::new(col(REGION_ID)),
396 Operator::Eq,
397 Box::new(Expr::Literal(
398 ScalarValue::UInt64(Some(target_region_id)),
399 None,
400 )),
401 ));
402 let request = ScanRequest {
403 filters: vec![filter],
404 ..Default::default()
405 };
406 let predicates = Predicates::from_scan_request(&Some(request));
407
408 builder.add_region_peers(
409 "greptime",
410 "public",
411 "test_table",
412 &predicates,
413 table_id,
414 &routes,
415 );
416
417 let batch = builder.finish().unwrap();
418 assert_eq!(batch.num_rows(), 1);
420 let region_id_col = batch
422 .column(3)
423 .as_primitive::<arrow::datatypes::UInt64Type>();
424 assert_eq!(region_id_col.value(0), target_region_id);
425 }
426}