1use std::sync::Arc;
16
17use common_base::hash::partition_expr_version;
18use common_error::ext::BoxedError;
19use common_meta::cache::{
20 CacheContainer, InitStrategy, Initializer, TableRoute, TableRouteCacheRef,
21};
22use common_meta::instruction::CacheIdent;
23use common_meta::rpc::router::RegionRoute;
24use moka::future::Cache;
25use snafu::ResultExt;
26use store_api::storage::{RegionId, TableId};
27
28use crate::expr::PartitionExpr;
29use crate::manager::PartitionInfoWithVersion;
30
31#[derive(Debug, Clone)]
32pub struct PhysicalPartitionInfo {
33 pub partitions: Vec<PartitionInfoWithVersion>,
34}
35
36#[derive(Debug, Clone)]
37pub enum CachedPartitionInfo {
38 Physical(Arc<PhysicalPartitionInfo>),
39 Logical(TableId),
40}
41
42impl CachedPartitionInfo {
43 pub fn into_physical(self) -> Option<Arc<PhysicalPartitionInfo>> {
45 match self {
46 CachedPartitionInfo::Physical(partitions) => Some(partitions),
47 CachedPartitionInfo::Logical(_) => None,
48 }
49 }
50}
51
52pub type PartitionInfoCache = CacheContainer<TableId, CachedPartitionInfo, CacheIdent>;
53
54pub type PartitionInfoCacheRef = Arc<PartitionInfoCache>;
55
56pub fn create_partitions_with_version_from_region_routes(
57 table_id: TableId,
58 region_routes: &[RegionRoute],
59) -> common_meta::error::Result<Vec<PartitionInfoWithVersion>> {
60 let mut partitions = Vec::with_capacity(region_routes.len());
61 for r in region_routes {
62 if r.is_ignore_all_writes() {
65 continue;
66 }
67
68 let expr_json = r.region.partition_expr();
69 let partition_expr_version = if expr_json.is_empty() {
70 None
71 } else {
72 Some(partition_expr_version(Some(expr_json.as_str())))
73 };
74 let partition_expr = PartitionExpr::from_json_str(expr_json.as_str())
75 .map_err(BoxedError::new)
76 .context(common_meta::error::ExternalSnafu)?;
77 let id = RegionId::new(table_id, r.region.id.region_number());
78 partitions.push(PartitionInfoWithVersion {
79 id,
80 partition_expr,
81 partition_expr_version,
82 });
83 }
84
85 Ok(partitions)
86}
87
88fn init_factory(
89 table_route_cache: TableRouteCacheRef,
90) -> Initializer<TableId, CachedPartitionInfo> {
91 Arc::new(move |table_id: &TableId| {
92 let table_route_cache = table_route_cache.clone();
93 Box::pin(async move {
94 let Some(table_route) = table_route_cache.get(*table_id).await? else {
95 return Ok(None);
96 };
97
98 let cached = match table_route.as_ref() {
99 TableRoute::Physical(physical) => {
100 let partitions = create_partitions_with_version_from_region_routes(
101 *table_id,
102 &physical.region_routes,
103 )?;
104
105 CachedPartitionInfo::Physical(Arc::new(PhysicalPartitionInfo { partitions }))
106 }
107 TableRoute::Logical(logical) => {
108 let table_id = logical.physical_table_id();
109 CachedPartitionInfo::Logical(table_id)
110 }
111 };
112
113 Ok(Some(cached))
114 })
115 })
116}
117
118pub fn new_partition_info_cache(
119 name: String,
120 cache: Cache<TableId, CachedPartitionInfo>,
121 table_route_cache: TableRouteCacheRef,
122) -> PartitionInfoCache {
123 CacheContainer::with_strategy(
124 name,
125 cache,
126 Box::new(|cache, idents| {
127 Box::pin(async move {
128 for ident in idents {
129 if let CacheIdent::TableId(table_id) = ident {
130 cache.invalidate(table_id).await
131 }
132 }
133 Ok(())
134 })
135 }),
136 init_factory(table_route_cache),
137 |ident| matches!(ident, CacheIdent::TableId(_)),
138 InitStrategy::VersionChecked,
139 )
140}
141
142#[cfg(test)]
143mod tests {
144 use common_base::hash::partition_expr_version;
145 use common_meta::rpc::router::{Region, RegionRoute, WriteRoutePolicy};
146 use store_api::storage::RegionId;
147
148 use super::create_partitions_with_version_from_region_routes;
149
150 #[test]
151 fn test_create_partitions_with_version_excludes_reject_all_writes() {
152 let table_id = 1024;
153 let expr_json =
154 r#"{"Expr":{"lhs":{"Column":"a"},"op":"GtEq","rhs":{"Value":{"UInt32":10}}}}"#;
155 let region_routes = vec![
156 RegionRoute {
157 region: Region {
158 id: RegionId::new(table_id, 1),
159 partition_expr: expr_json.to_string(),
160 ..Default::default()
161 },
162 leader_peer: None,
163 follower_peers: vec![],
164 leader_state: None,
165 leader_down_since: None,
166 write_route_policy: Some(WriteRoutePolicy::IgnoreAllWrites),
167 },
168 RegionRoute {
169 region: Region {
170 id: RegionId::new(table_id, 2),
171 partition_expr: expr_json.to_string(),
172 ..Default::default()
173 },
174 ..Default::default()
175 },
176 ];
177
178 let partitions =
179 create_partitions_with_version_from_region_routes(table_id, ®ion_routes).unwrap();
180 assert_eq!(1, partitions.len());
181 assert_eq!(RegionId::new(table_id, 2), partitions[0].id);
182 assert_eq!(
183 Some(partition_expr_version(Some(expr_json))),
184 partitions[0].partition_expr_version
185 );
186 }
187}