mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-05-24 00:40:40 +00:00
refactor!: trying to replace TableGlobalValue, part 2 (#1985)
* refactor!: using the new table metadata values * fix: resolve PR comments * fix: resolve PR comments * fix: resolve PR comments
This commit is contained in:
@@ -9,8 +9,6 @@ async-trait = "0.1"
|
||||
common-error = { path = "../error" }
|
||||
common-telemetry = { path = "../telemetry" }
|
||||
datatypes = { path = "../../datatypes" }
|
||||
lazy_static = "1.4"
|
||||
regex = "1.6"
|
||||
serde.workspace = true
|
||||
serde_json = "1.0"
|
||||
snafu = { version = "0.7", features = ["backtraces"] }
|
||||
|
||||
@@ -59,8 +59,23 @@ pub enum Error {
|
||||
#[snafu(display("Invalid protobuf message, err: {}", err_msg))]
|
||||
InvalidProtoMsg { err_msg: String, location: Location },
|
||||
|
||||
#[snafu(display("Concurrent modify regions placement: {err_msg}"))]
|
||||
ConcurrentModifyRegionsPlacement { err_msg: String, location: Location },
|
||||
#[snafu(display("Unexpected: {err_msg}"))]
|
||||
Unexpected { err_msg: String, location: Location },
|
||||
|
||||
#[snafu(display("Table already exists, table_id: {}", table_id))]
|
||||
TableAlreadyExists {
|
||||
table_id: TableId,
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display("Table does not exist, table_name: {}", table_name))]
|
||||
TableNotExist {
|
||||
table_name: String,
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to rename table, reason: {}", reason))]
|
||||
RenameTable { reason: String, location: Location },
|
||||
|
||||
#[snafu(display("Invalid table metadata, err: {}", err_msg))]
|
||||
InvalidTableMetadata { err_msg: String, location: Location },
|
||||
@@ -112,12 +127,15 @@ impl ErrorExt for Error {
|
||||
| RouteInfoCorrupted { .. }
|
||||
| InvalidProtoMsg { .. }
|
||||
| InvalidTableMetadata { .. }
|
||||
| MoveRegion { .. } => StatusCode::Unexpected,
|
||||
| MoveRegion { .. }
|
||||
| Unexpected { .. } => StatusCode::Unexpected,
|
||||
|
||||
SendMessage { .. }
|
||||
| GetKvCache { .. }
|
||||
| CacheNotGet { .. }
|
||||
| ConcurrentModifyRegionsPlacement { .. } => StatusCode::Internal,
|
||||
| TableAlreadyExists { .. }
|
||||
| TableNotExist { .. }
|
||||
| RenameTable { .. } => StatusCode::Internal,
|
||||
|
||||
EncodeJson { .. } | DecodeJson { .. } | PayloadNotExist { .. } => {
|
||||
StatusCode::Unexpected
|
||||
|
||||
@@ -20,42 +20,24 @@ use common_catalog::error::{
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use snafu::{ensure, OptionExt, ResultExt};
|
||||
use table::metadata::{RawTableInfo, TableId, TableVersion};
|
||||
use table::metadata::{RawTableInfo, TableId};
|
||||
|
||||
pub const CATALOG_KEY_PREFIX: &str = "__c";
|
||||
pub const SCHEMA_KEY_PREFIX: &str = "__s";
|
||||
pub const TABLE_GLOBAL_KEY_PREFIX: &str = "__tg";
|
||||
pub const TABLE_REGIONAL_KEY_PREFIX: &str = "__tr";
|
||||
|
||||
const ALPHANUMERICS_NAME_PATTERN: &str = "[a-zA-Z_][a-zA-Z0-9_]*";
|
||||
const TABLE_NAME_PATTERN: &str = "[a-zA-Z_:][a-zA-Z0-9_:]*";
|
||||
/// The pattern of a valid catalog, schema or table name.
|
||||
const NAME_PATTERN: &str = "[a-zA-Z_:][a-zA-Z0-9_:]*";
|
||||
|
||||
lazy_static! {
|
||||
static ref CATALOG_KEY_PATTERN: Regex = Regex::new(&format!(
|
||||
"^{CATALOG_KEY_PREFIX}-({ALPHANUMERICS_NAME_PATTERN})$"
|
||||
))
|
||||
.unwrap();
|
||||
static ref CATALOG_KEY_PATTERN: Regex =
|
||||
Regex::new(&format!("^{CATALOG_KEY_PREFIX}-({NAME_PATTERN})$")).unwrap();
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref SCHEMA_KEY_PATTERN: Regex = Regex::new(&format!(
|
||||
"^{SCHEMA_KEY_PREFIX}-({ALPHANUMERICS_NAME_PATTERN})-({ALPHANUMERICS_NAME_PATTERN})$"
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref TABLE_GLOBAL_KEY_PATTERN: Regex = Regex::new(&format!(
|
||||
"^{TABLE_GLOBAL_KEY_PREFIX}-({ALPHANUMERICS_NAME_PATTERN})-({ALPHANUMERICS_NAME_PATTERN})-({TABLE_NAME_PATTERN})$"
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref TABLE_REGIONAL_KEY_PATTERN: Regex = Regex::new(&format!(
|
||||
"^{TABLE_REGIONAL_KEY_PREFIX}-({ALPHANUMERICS_NAME_PATTERN})-({ALPHANUMERICS_NAME_PATTERN})-({TABLE_NAME_PATTERN})-([0-9]+)$"
|
||||
"^{SCHEMA_KEY_PREFIX}-({NAME_PATTERN})-({NAME_PATTERN})$"
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
@@ -68,75 +50,6 @@ pub fn build_schema_prefix(catalog_name: impl AsRef<str>) -> String {
|
||||
format!("{SCHEMA_KEY_PREFIX}-{}-", catalog_name.as_ref())
|
||||
}
|
||||
|
||||
/// Global table info has only one key across all datanodes so it does not have `node_id` field.
|
||||
pub fn build_table_global_prefix(
|
||||
catalog_name: impl AsRef<str>,
|
||||
schema_name: impl AsRef<str>,
|
||||
) -> String {
|
||||
format!(
|
||||
"{TABLE_GLOBAL_KEY_PREFIX}-{}-{}-",
|
||||
catalog_name.as_ref(),
|
||||
schema_name.as_ref()
|
||||
)
|
||||
}
|
||||
|
||||
/// Regional table info varies between datanode, so it contains a `node_id` field.
|
||||
pub fn build_table_regional_prefix(
|
||||
catalog_name: impl AsRef<str>,
|
||||
schema_name: impl AsRef<str>,
|
||||
) -> String {
|
||||
format!(
|
||||
"{}-{}-{}-",
|
||||
TABLE_REGIONAL_KEY_PREFIX,
|
||||
catalog_name.as_ref(),
|
||||
schema_name.as_ref()
|
||||
)
|
||||
}
|
||||
|
||||
/// Table global info has only one key across all datanodes so it does not have `node_id` field.
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub struct TableGlobalKey {
|
||||
pub catalog_name: String,
|
||||
pub schema_name: String,
|
||||
pub table_name: String,
|
||||
}
|
||||
|
||||
impl Display for TableGlobalKey {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(TABLE_GLOBAL_KEY_PREFIX)?;
|
||||
f.write_str("-")?;
|
||||
f.write_str(&self.catalog_name)?;
|
||||
f.write_str("-")?;
|
||||
f.write_str(&self.schema_name)?;
|
||||
f.write_str("-")?;
|
||||
f.write_str(&self.table_name)
|
||||
}
|
||||
}
|
||||
|
||||
impl TableGlobalKey {
|
||||
pub fn parse<S: AsRef<str>>(s: S) -> Result<Self, Error> {
|
||||
let key = s.as_ref();
|
||||
let captures = TABLE_GLOBAL_KEY_PATTERN
|
||||
.captures(key)
|
||||
.context(InvalidCatalogSnafu { key })?;
|
||||
ensure!(captures.len() == 4, InvalidCatalogSnafu { key });
|
||||
|
||||
Ok(Self {
|
||||
catalog_name: captures[1].to_string(),
|
||||
schema_name: captures[2].to_string(),
|
||||
table_name: captures[3].to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_raw_key(&self) -> Vec<u8> {
|
||||
self.to_string().into_bytes()
|
||||
}
|
||||
|
||||
pub fn try_from_raw_key(key: &[u8]) -> Result<Self, Error> {
|
||||
Self::parse(String::from_utf8_lossy(key))
|
||||
}
|
||||
}
|
||||
|
||||
/// Table global info contains necessary info for a datanode to create table regions, including
|
||||
/// table id, table meta(schema...), region id allocation across datanodes.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
@@ -152,64 +65,6 @@ impl TableGlobalValue {
|
||||
pub fn table_id(&self) -> TableId {
|
||||
self.table_info.ident.table_id
|
||||
}
|
||||
|
||||
pub fn engine(&self) -> &str {
|
||||
&self.table_info.meta.engine
|
||||
}
|
||||
}
|
||||
|
||||
/// Table regional info that varies between datanode, so it contains a `node_id` field.
|
||||
pub struct TableRegionalKey {
|
||||
pub catalog_name: String,
|
||||
pub schema_name: String,
|
||||
pub table_name: String,
|
||||
pub node_id: u64,
|
||||
}
|
||||
|
||||
impl Display for TableRegionalKey {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(TABLE_REGIONAL_KEY_PREFIX)?;
|
||||
f.write_str("-")?;
|
||||
f.write_str(&self.catalog_name)?;
|
||||
f.write_str("-")?;
|
||||
f.write_str(&self.schema_name)?;
|
||||
f.write_str("-")?;
|
||||
f.write_str(&self.table_name)?;
|
||||
f.write_str("-")?;
|
||||
f.serialize_u64(self.node_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRegionalKey {
|
||||
pub fn parse<S: AsRef<str>>(s: S) -> Result<Self, Error> {
|
||||
let key = s.as_ref();
|
||||
let captures = TABLE_REGIONAL_KEY_PATTERN
|
||||
.captures(key)
|
||||
.context(InvalidCatalogSnafu { key })?;
|
||||
ensure!(captures.len() == 5, InvalidCatalogSnafu { key });
|
||||
let node_id = captures[4]
|
||||
.to_string()
|
||||
.parse()
|
||||
.map_err(|_| InvalidCatalogSnafu { key }.build())?;
|
||||
Ok(Self {
|
||||
catalog_name: captures[1].to_string(),
|
||||
schema_name: captures[2].to_string(),
|
||||
table_name: captures[3].to_string(),
|
||||
node_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Regional table info of specific datanode, including table version on that datanode and
|
||||
/// region ids allocated by metasrv.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct TableRegionalValue {
|
||||
// We can remove the `Option` from the table id once all regional values
|
||||
// stored in meta have table ids.
|
||||
pub table_id: Option<TableId>,
|
||||
pub version: TableVersion,
|
||||
pub regions_ids: Vec<u32>,
|
||||
pub engine_name: Option<String>,
|
||||
}
|
||||
|
||||
pub struct CatalogKey {
|
||||
@@ -295,19 +150,10 @@ macro_rules! define_catalog_value {
|
||||
}
|
||||
}
|
||||
|
||||
define_catalog_value!(
|
||||
TableRegionalValue,
|
||||
TableGlobalValue,
|
||||
CatalogValue,
|
||||
SchemaValue
|
||||
);
|
||||
define_catalog_value!(TableGlobalValue, CatalogValue, SchemaValue);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use datatypes::prelude::ConcreteDataType;
|
||||
use datatypes::schema::{ColumnSchema, RawSchema, Schema};
|
||||
use table::metadata::{RawTableMeta, TableIdent, TableType};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -326,95 +172,4 @@ mod tests {
|
||||
assert_eq!("S", schema_key.schema_name);
|
||||
assert_eq!(key, schema_key.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_table_key() {
|
||||
let key = "__tg-C-S-T";
|
||||
let entry = TableGlobalKey::parse(key).unwrap();
|
||||
assert_eq!("C", entry.catalog_name);
|
||||
assert_eq!("S", entry.schema_name);
|
||||
assert_eq!("T", entry.table_name);
|
||||
assert_eq!(key, &entry.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_prefix() {
|
||||
assert_eq!("__c-", build_catalog_prefix());
|
||||
assert_eq!("__s-CATALOG-", build_schema_prefix("CATALOG"));
|
||||
assert_eq!(
|
||||
"__tg-CATALOG-SCHEMA-",
|
||||
build_table_global_prefix("CATALOG", "SCHEMA")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize_schema() {
|
||||
let schema = Schema::new(vec![ColumnSchema::new(
|
||||
"name",
|
||||
ConcreteDataType::string_datatype(),
|
||||
true,
|
||||
)]);
|
||||
|
||||
let meta = RawTableMeta {
|
||||
schema: RawSchema::from(&schema),
|
||||
engine: "mito".to_string(),
|
||||
created_on: chrono::DateTime::default(),
|
||||
primary_key_indices: vec![0, 1],
|
||||
next_column_id: 3,
|
||||
engine_options: Default::default(),
|
||||
value_indices: vec![2, 3],
|
||||
options: Default::default(),
|
||||
region_numbers: vec![1],
|
||||
};
|
||||
|
||||
let table_info = RawTableInfo {
|
||||
ident: TableIdent {
|
||||
table_id: 42,
|
||||
version: 1,
|
||||
},
|
||||
name: "table_1".to_string(),
|
||||
desc: Some("blah".to_string()),
|
||||
catalog_name: "catalog_1".to_string(),
|
||||
schema_name: "schema_1".to_string(),
|
||||
meta,
|
||||
table_type: TableType::Base,
|
||||
};
|
||||
|
||||
let value = TableGlobalValue {
|
||||
node_id: 0,
|
||||
regions_id_map: HashMap::from([(0, vec![1, 2, 3])]),
|
||||
table_info,
|
||||
};
|
||||
let serialized = serde_json::to_string(&value).unwrap();
|
||||
let deserialized = TableGlobalValue::parse(serialized).unwrap();
|
||||
assert_eq!(value, deserialized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_global_value_compatibility() {
|
||||
let s = r#"{"node_id":1,"regions_id_map":{"1":[0]},"table_info":{"ident":{"table_id":1098,"version":1},"name":"container_cpu_limit","desc":"Created on insertion","catalog_name":"greptime","schema_name":"dd","meta":{"schema":{"column_schemas":[{"name":"container_id","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"container_name","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"docker_image","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"host","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"image_name","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"image_tag","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"interval","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"runtime","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"short_image","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"type","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"dd_value","data_type":{"Float64":{}},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"ts","data_type":{"Timestamp":{"Millisecond":null}},"is_nullable":false,"is_time_index":true,"default_constraint":null,"metadata":{"greptime:time_index":"true"}},{"name":"git.repository_url","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}}],"timestamp_index":11,"version":1},"primary_key_indices":[0,1,2,3,4,5,6,7,8,9,12],"value_indices":[10,11],"engine":"mito","next_column_id":12,"region_numbers":[],"engine_options":{},"options":{},"created_on":"1970-01-01T00:00:00Z"},"table_type":"Base"}}"#;
|
||||
let _ = TableGlobalValue::parse(s).unwrap();
|
||||
}
|
||||
|
||||
fn test_valid_table_patterns(table_name: &str) {
|
||||
assert_eq!(
|
||||
table_name,
|
||||
TableGlobalKey::parse(format!("__tg-catalog-schema-{}", table_name))
|
||||
.unwrap()
|
||||
.table_name
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
table_name,
|
||||
TableRegionalKey::parse(format!("__tr-catalog-schema-{}-0", table_name))
|
||||
.unwrap()
|
||||
.table_name
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_name_pattern() {
|
||||
test_valid_table_patterns("cpu:metrics");
|
||||
test_valid_table_patterns(":cpu:metrics");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,13 +18,11 @@ use store_api::storage::RegionNumber;
|
||||
use table::metadata::TableId;
|
||||
|
||||
use super::{DATANODE_TABLE_KEY_PATTERN, DATANODE_TABLE_KEY_PREFIX};
|
||||
use crate::error::{
|
||||
ConcurrentModifyRegionsPlacementSnafu, InvalidTableMetadataSnafu, MoveRegionSnafu, Result,
|
||||
};
|
||||
use crate::error::{InvalidTableMetadataSnafu, MoveRegionSnafu, Result, UnexpectedSnafu};
|
||||
use crate::key::{to_removed_key, TableMetaKey};
|
||||
use crate::kv_backend::txn::{Compare, CompareOp, Txn, TxnOp};
|
||||
use crate::kv_backend::KvBackendRef;
|
||||
use crate::rpc::store::{CompareAndPutRequest, MoveValueRequest, RangeRequest};
|
||||
use crate::rpc::store::{BatchGetRequest, CompareAndPutRequest, MoveValueRequest, RangeRequest};
|
||||
use crate::DatanodeId;
|
||||
|
||||
pub struct DatanodeTableKey {
|
||||
@@ -99,7 +97,7 @@ impl DatanodeTableManager {
|
||||
Self { kv_backend }
|
||||
}
|
||||
|
||||
async fn get(&self, key: &DatanodeTableKey) -> Result<Option<DatanodeTableValue>> {
|
||||
pub async fn get(&self, key: &DatanodeTableKey) -> Result<Option<DatanodeTableValue>> {
|
||||
self.kv_backend
|
||||
.get(&key.as_raw_key())
|
||||
.await?
|
||||
@@ -107,31 +105,36 @@ impl DatanodeTableManager {
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Create DatanodeTable key and value. If the key already exists, check if the value is the same.
|
||||
pub async fn create(
|
||||
&self,
|
||||
datanode_id: DatanodeId,
|
||||
table_id: TableId,
|
||||
regions: Vec<RegionNumber>,
|
||||
) -> Result<()> {
|
||||
let key = DatanodeTableKey::new(datanode_id, table_id).as_raw_key();
|
||||
let val = DatanodeTableValue::new(table_id, regions).try_as_raw_value()?;
|
||||
let req = CompareAndPutRequest::new().with_key(key).with_value(val);
|
||||
let key = DatanodeTableKey::new(datanode_id, table_id);
|
||||
let val = DatanodeTableValue::new(table_id, regions.clone());
|
||||
let req = CompareAndPutRequest::new()
|
||||
.with_key(key.as_raw_key())
|
||||
.with_value(val.try_as_raw_value()?);
|
||||
|
||||
let resp = self.kv_backend.compare_and_put(req).await?;
|
||||
if !resp.success {
|
||||
let curr = resp.prev_kv.map_or_else(
|
||||
|| "empty".to_string(),
|
||||
|kv| {
|
||||
DatanodeTableValue::try_from_raw_value(kv.value).map_or_else(
|
||||
|e| format!("Invalid DatanodeTableValue for Datanode {datanode_id}: {e}"),
|
||||
|v| format!("{v:?}"),
|
||||
)
|
||||
},
|
||||
let Some(curr) = resp
|
||||
.prev_kv
|
||||
.map(|kv| DatanodeTableValue::try_from_raw_value(kv.value))
|
||||
.transpose()? else {
|
||||
return UnexpectedSnafu {
|
||||
err_msg: format!("compare_and_put expect None but failed with current value None, key: {key}, val: {val:?}"),
|
||||
}.fail();
|
||||
};
|
||||
|
||||
ensure!(
|
||||
curr.table_id == table_id && curr.regions == regions,
|
||||
UnexpectedSnafu {
|
||||
err_msg: format!("current value '{curr:?}' already existed for key '{key}', {val:?} is not set"),
|
||||
}
|
||||
);
|
||||
return ConcurrentModifyRegionsPlacementSnafu {
|
||||
err_msg: format!("Datanode {datanode_id} already existed {curr}"),
|
||||
}
|
||||
.fail();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -150,13 +153,26 @@ impl DatanodeTableManager {
|
||||
to_datanode: DatanodeId,
|
||||
table_id: TableId,
|
||||
region: RegionNumber,
|
||||
) -> Result<bool> {
|
||||
) -> Result<()> {
|
||||
let from_key = DatanodeTableKey::new(from_datanode, table_id);
|
||||
let mut from_value = self.get(&from_key).await?.context(MoveRegionSnafu {
|
||||
table_id,
|
||||
region,
|
||||
err_msg: format!("DatanodeTableKey not found for Datanode {from_datanode}"),
|
||||
})?;
|
||||
let to_key = DatanodeTableKey::new(to_datanode, table_id);
|
||||
let mut kvs = self
|
||||
.kv_backend
|
||||
.batch_get(BatchGetRequest {
|
||||
keys: vec![from_key.as_raw_key(), to_key.as_raw_key()],
|
||||
})
|
||||
.await?
|
||||
.kvs;
|
||||
|
||||
ensure!(
|
||||
!kvs.is_empty(),
|
||||
MoveRegionSnafu {
|
||||
table_id,
|
||||
region,
|
||||
err_msg: format!("DatanodeTableKey not found for Datanode {from_datanode}"),
|
||||
}
|
||||
);
|
||||
let mut from_value = DatanodeTableValue::try_from_raw_value(kvs.remove(0).value)?;
|
||||
|
||||
ensure!(
|
||||
from_value.regions.contains(®ion),
|
||||
@@ -167,8 +183,11 @@ impl DatanodeTableManager {
|
||||
}
|
||||
);
|
||||
|
||||
let to_key = DatanodeTableKey::new(to_datanode, table_id);
|
||||
let to_value = self.get(&to_key).await?;
|
||||
let to_value = if !kvs.is_empty() {
|
||||
Some(DatanodeTableValue::try_from_raw_value(kvs.remove(0).value)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(v) = to_value.as_ref() {
|
||||
ensure!(
|
||||
@@ -221,7 +240,15 @@ impl DatanodeTableManager {
|
||||
|
||||
let txn = Txn::new().when(compares).and_then(operations);
|
||||
let resp = self.kv_backend.txn(txn).await?;
|
||||
Ok(resp.succeeded)
|
||||
ensure!(
|
||||
resp.succeeded,
|
||||
MoveRegionSnafu {
|
||||
table_id,
|
||||
region,
|
||||
err_msg: format!("txn failed with responses: {:?}", resp.responses),
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn tables(&self, datanode_id: DatanodeId) -> Result<Vec<DatanodeTableValue>> {
|
||||
@@ -262,7 +289,7 @@ mod tests {
|
||||
|
||||
// Move region 1 from datanode 1 to datanode 2.
|
||||
// Note that the DatanodeTableValue is not existed for datanode 2 now.
|
||||
assert!(manager.move_region(1, 2, 1, 1).await.unwrap());
|
||||
assert!(manager.move_region(1, 2, 1, 1).await.is_ok());
|
||||
let value = manager
|
||||
.get(&DatanodeTableKey::new(1, 1))
|
||||
.await
|
||||
@@ -348,12 +375,15 @@ mod tests {
|
||||
assert!(manager.create(2, 1, vec![4, 5, 6]).await.is_ok());
|
||||
assert!(manager.create(2, 2, vec![1, 2, 3]).await.is_ok());
|
||||
|
||||
// If the value is the same, "create" can be called again.
|
||||
assert!(manager.create(2, 2, vec![1, 2, 3]).await.is_ok());
|
||||
|
||||
let err_msg = manager
|
||||
.create(1, 1, vec![4, 5, 6])
|
||||
.await
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
assert!(err_msg.contains("Concurrent modify regions placement: Datanode 1 already existed DatanodeTableValue { table_id: 1, regions: [1, 2, 3], version: 0 }"));
|
||||
assert!(err_msg.contains("Unexpected: current value 'DatanodeTableValue { table_id: 1, regions: [1, 2, 3], version: 0 }' already existed for key '__dn_table/1/1', DatanodeTableValue { table_id: 1, regions: [4, 5, 6], version: 0 } is not set"));
|
||||
|
||||
let to_be_removed_key = DatanodeTableKey::new(2, 1);
|
||||
let expected_value = DatanodeTableValue {
|
||||
|
||||
@@ -13,16 +13,14 @@
|
||||
// limitations under the License.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use snafu::ResultExt;
|
||||
use snafu::ensure;
|
||||
use table::metadata::{RawTableInfo, TableId};
|
||||
|
||||
use super::TABLE_INFO_KEY_PREFIX;
|
||||
use crate::error::{InvalidCatalogValueSnafu, Result};
|
||||
use crate::helper::{TableGlobalKey, TableGlobalValue};
|
||||
use crate::error::{Result, UnexpectedSnafu};
|
||||
use crate::key::{to_removed_key, TableMetaKey};
|
||||
use crate::kv_backend::KvBackendRef;
|
||||
use crate::rpc::store::{CompareAndPutRequest, MoveValueRequest, PutRequest};
|
||||
use crate::table_name::TableName;
|
||||
use crate::rpc::store::{CompareAndPutRequest, MoveValueRequest};
|
||||
|
||||
pub struct TableInfoKey {
|
||||
table_id: TableId,
|
||||
@@ -64,60 +62,6 @@ impl TableInfoManager {
|
||||
Self { kv_backend }
|
||||
}
|
||||
|
||||
// TODO(LFC): Remove this method when table metadata refactor is done.
|
||||
pub async fn get_old(&self, table_name: &TableName) -> Result<Option<TableInfoValue>> {
|
||||
let table_global_key = TableGlobalKey {
|
||||
catalog_name: table_name.catalog_name.clone(),
|
||||
schema_name: table_name.schema_name.clone(),
|
||||
table_name: table_name.table_name.clone(),
|
||||
};
|
||||
self.kv_backend
|
||||
.get(table_global_key.to_string().as_bytes())
|
||||
.await?
|
||||
.map(|kv| TableGlobalValue::from_bytes(kv.value))
|
||||
.transpose()
|
||||
.map(|v| {
|
||||
v.map(|v| TableInfoValue {
|
||||
table_info: v.table_info,
|
||||
version: 0,
|
||||
})
|
||||
})
|
||||
.context(InvalidCatalogValueSnafu)
|
||||
}
|
||||
|
||||
// TODO(LFC): Remove this method when table metadata refactor is done.
|
||||
pub async fn put_old(&self, table_info: RawTableInfo) -> Result<()> {
|
||||
let key = TableGlobalKey {
|
||||
catalog_name: table_info.catalog_name.clone(),
|
||||
schema_name: table_info.schema_name.clone(),
|
||||
table_name: table_info.name.clone(),
|
||||
}
|
||||
.to_string();
|
||||
let raw_key = key.as_bytes();
|
||||
|
||||
let regions_id_map = self
|
||||
.kv_backend
|
||||
.get(raw_key)
|
||||
.await?
|
||||
.map(|kv| TableGlobalValue::from_bytes(kv.value()))
|
||||
.transpose()
|
||||
.context(InvalidCatalogValueSnafu)?
|
||||
.map(|v| v.regions_id_map)
|
||||
.unwrap_or_default();
|
||||
|
||||
let raw_value = TableGlobalValue {
|
||||
node_id: 0,
|
||||
regions_id_map,
|
||||
table_info,
|
||||
}
|
||||
.as_bytes()
|
||||
.context(InvalidCatalogValueSnafu)?;
|
||||
|
||||
let req = PutRequest::new().with_key(raw_key).with_value(raw_value);
|
||||
self.kv_backend.put(req).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get(&self, table_id: TableId) -> Result<Option<TableInfoValue>> {
|
||||
let key = TableInfoKey::new(table_id);
|
||||
let raw_key = key.as_raw_key();
|
||||
@@ -128,6 +72,29 @@ impl TableInfoManager {
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Create TableInfo key and value. If the key already exists, check if the value is the same.
|
||||
pub async fn create(&self, table_id: TableId, table_info: &RawTableInfo) -> Result<()> {
|
||||
let result = self
|
||||
.compare_and_put(table_id, None, table_info.clone())
|
||||
.await?;
|
||||
if let Err(curr) = result {
|
||||
let Some(curr) = curr else {
|
||||
return UnexpectedSnafu {
|
||||
err_msg: format!("compare_and_put expect None but failed with current value None, table_id: {table_id}, table_info: {table_info:?}"),
|
||||
}.fail()
|
||||
};
|
||||
ensure!(
|
||||
&curr.table_info == table_info,
|
||||
UnexpectedSnafu {
|
||||
err_msg: format!(
|
||||
"TableInfoValue for table {table_id} is updated before it is created!"
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compare and put value of key. `expect` is the expected value, if backend's current value associated
|
||||
/// with key is the same as `expect`, the value will be updated to `val`.
|
||||
///
|
||||
@@ -211,6 +178,13 @@ mod tests {
|
||||
}
|
||||
|
||||
let manager = TableInfoManager::new(backend.clone());
|
||||
assert!(manager.create(99, &new_table_info(99)).await.is_ok());
|
||||
assert!(manager.create(99, &new_table_info(99)).await.is_ok());
|
||||
|
||||
let result = manager.create(99, &new_table_info(88)).await;
|
||||
let err_msg = result.unwrap_err().to_string();
|
||||
assert!(err_msg
|
||||
.contains("Unexpected: TableInfoValue for table 99 is updated before it is created!"));
|
||||
|
||||
let val = manager.get(1).await.unwrap().unwrap();
|
||||
assert_eq!(
|
||||
|
||||
@@ -15,14 +15,17 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use snafu::{OptionExt, ResultExt};
|
||||
use snafu::{ensure, OptionExt};
|
||||
use table::metadata::TableId;
|
||||
|
||||
use super::{TABLE_NAME_KEY_PATTERN, TABLE_NAME_KEY_PREFIX};
|
||||
use crate::error::{Error, InvalidCatalogValueSnafu, InvalidTableMetadataSnafu, Result};
|
||||
use crate::helper::{build_table_global_prefix, TableGlobalKey, TableGlobalValue};
|
||||
use crate::error::{
|
||||
Error, InvalidTableMetadataSnafu, RenameTableSnafu, Result, TableAlreadyExistsSnafu,
|
||||
TableNotExistSnafu, UnexpectedSnafu,
|
||||
};
|
||||
use crate::key::{to_removed_key, TableMetaKey};
|
||||
use crate::kv_backend::memory::MemoryKvBackend;
|
||||
use crate::kv_backend::txn::{Compare, CompareOp, Txn, TxnOp};
|
||||
use crate::kv_backend::KvBackendRef;
|
||||
use crate::rpc::store::{CompareAndPutRequest, MoveValueRequest, RangeRequest};
|
||||
use crate::table_name::TableName;
|
||||
@@ -147,12 +150,8 @@ impl TableNameManager {
|
||||
Self { kv_backend }
|
||||
}
|
||||
|
||||
/// Creates a new table name entry. Returns the current [TableNameValue] if the entry already existed.
|
||||
pub async fn create(
|
||||
&self,
|
||||
key: &TableNameKey<'_>,
|
||||
table_id: TableId,
|
||||
) -> Result<Option<TableNameValue>> {
|
||||
/// Create TableName key and value. If the key already exists, check if the value is the same.
|
||||
pub async fn create(&self, key: &TableNameKey<'_>, table_id: TableId) -> Result<()> {
|
||||
let raw_key = key.as_raw_key();
|
||||
let value = TableNameValue::new(table_id);
|
||||
let raw_value = value.try_as_raw_value()?;
|
||||
@@ -160,49 +159,87 @@ impl TableNameManager {
|
||||
.with_key(raw_key)
|
||||
.with_value(raw_value);
|
||||
let result = self.kv_backend.compare_and_put(req).await?;
|
||||
Ok(if result.success {
|
||||
None
|
||||
} else {
|
||||
result
|
||||
if !result.success {
|
||||
let Some(curr) = result
|
||||
.prev_kv
|
||||
.map(|x| TableNameValue::try_from_raw_value(x.value))
|
||||
.transpose()?
|
||||
})
|
||||
}
|
||||
|
||||
// TODO(LFC): Remove this method when table metadata refactor is done.
|
||||
pub async fn get_old(&self, key: &TableNameKey<'_>) -> Result<Option<TableNameValue>> {
|
||||
let table_global_key = TableGlobalKey {
|
||||
catalog_name: key.catalog.to_string(),
|
||||
schema_name: key.schema.to_string(),
|
||||
table_name: key.table.to_string(),
|
||||
};
|
||||
self.kv_backend
|
||||
.get(table_global_key.to_string().as_bytes())
|
||||
.await?
|
||||
.map(|kv| TableGlobalValue::from_bytes(kv.value()))
|
||||
.transpose()
|
||||
.map(|v| v.map(|v| TableNameValue::new(v.table_id())))
|
||||
.context(InvalidCatalogValueSnafu)
|
||||
}
|
||||
|
||||
// TODO(LFC): Remove this method when table metadata refactor is done.
|
||||
pub async fn tables_old(&self, catalog: &str, schema: &str) -> Result<Vec<String>> {
|
||||
let key = build_table_global_prefix(catalog, schema);
|
||||
let req = RangeRequest::new().with_prefix(key.as_bytes());
|
||||
|
||||
let resp = self.kv_backend.range(req).await?;
|
||||
|
||||
let mut table_names = Vec::with_capacity(resp.kvs.len());
|
||||
for kv in resp.kvs {
|
||||
let key = TableGlobalKey::parse(String::from_utf8_lossy(kv.key()))
|
||||
.context(InvalidCatalogValueSnafu)?;
|
||||
table_names.push(key.table_name);
|
||||
.transpose()? else {
|
||||
return UnexpectedSnafu {
|
||||
err_msg: format!("compare_and_put expect None but failed with current value None, key: {key}, value: {value:?}"),
|
||||
}.fail()
|
||||
};
|
||||
ensure!(
|
||||
curr.table_id == table_id,
|
||||
TableAlreadyExistsSnafu {
|
||||
table_id: curr.table_id
|
||||
}
|
||||
);
|
||||
}
|
||||
Ok(table_names)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get(&self, key: &TableNameKey<'_>) -> Result<Option<TableNameValue>> {
|
||||
/// Rename a TableNameKey to a new table name. Will check whether the TableNameValue matches the
|
||||
/// `expected_table_id` first. Can be executed again if the first invocation is successful.
|
||||
pub async fn rename(
|
||||
&self,
|
||||
key: TableNameKey<'_>,
|
||||
expected_table_id: TableId,
|
||||
new_table_name: &str,
|
||||
) -> Result<()> {
|
||||
let new_key = TableNameKey::new(key.catalog, key.schema, new_table_name);
|
||||
|
||||
if let Some(value) = self.get(key).await? {
|
||||
ensure!(
|
||||
value.table_id == expected_table_id,
|
||||
RenameTableSnafu {
|
||||
reason: format!(
|
||||
"the input table name '{}' and id '{expected_table_id}' not match",
|
||||
Into::<TableName>::into(key)
|
||||
),
|
||||
}
|
||||
);
|
||||
|
||||
let txn = Txn::new()
|
||||
.when(vec![
|
||||
Compare::with_value(
|
||||
key.as_raw_key(),
|
||||
CompareOp::Equal,
|
||||
value.try_as_raw_value()?,
|
||||
),
|
||||
Compare::with_not_exist_value(new_key.as_raw_key(), CompareOp::Equal),
|
||||
])
|
||||
.and_then(vec![
|
||||
TxnOp::Delete(key.as_raw_key()),
|
||||
TxnOp::Put(new_key.as_raw_key(), value.try_as_raw_value()?),
|
||||
]);
|
||||
|
||||
let resp = self.kv_backend.txn(txn).await?;
|
||||
ensure!(
|
||||
resp.succeeded,
|
||||
RenameTableSnafu {
|
||||
reason: format!("txn failed with response: {:?}", resp.responses)
|
||||
}
|
||||
);
|
||||
} else {
|
||||
let Some(value) = self.get(new_key).await? else {
|
||||
// If we can't get the table by its original name, nor can we get by its altered
|
||||
// name, then the table must not exist at the first place.
|
||||
return TableNotExistSnafu {
|
||||
table_name: TableName::from(key).to_string(),
|
||||
}.fail();
|
||||
};
|
||||
|
||||
ensure!(
|
||||
value.table_id == expected_table_id,
|
||||
TableAlreadyExistsSnafu {
|
||||
table_id: value.table_id
|
||||
}
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get(&self, key: TableNameKey<'_>) -> Result<Option<TableNameValue>> {
|
||||
let raw_key = key.as_raw_key();
|
||||
self.kv_backend
|
||||
.get(&raw_key)
|
||||
@@ -223,7 +260,7 @@ impl TableNameManager {
|
||||
Ok(table_names)
|
||||
}
|
||||
|
||||
pub async fn remove(&self, key: &TableNameKey<'_>) -> Result<()> {
|
||||
pub async fn remove(&self, key: TableNameKey<'_>) -> Result<()> {
|
||||
let raw_key = key.as_raw_key();
|
||||
let removed_key = to_removed_key(&String::from_utf8_lossy(&raw_key));
|
||||
let req = MoveValueRequest::new(raw_key, removed_key.as_bytes());
|
||||
@@ -248,21 +285,23 @@ mod tests {
|
||||
for i in 1..=3 {
|
||||
let table_name = format!("table_{}", i);
|
||||
let key = TableNameKey::new("my_catalog", "my_schema", &table_name);
|
||||
assert!(manager.create(&key, i).await.unwrap().is_none());
|
||||
assert!(manager.create(&key, i).await.is_ok());
|
||||
}
|
||||
|
||||
let key = TableNameKey::new("my_catalog", "my_schema", "my_table");
|
||||
assert!(manager.create(&key, 99).await.unwrap().is_none());
|
||||
assert!(manager.create(&key, 99).await.is_ok());
|
||||
assert!(manager.create(&key, 99).await.is_ok());
|
||||
|
||||
let curr = manager.create(&key, 9).await.unwrap();
|
||||
assert_eq!(Some(TableNameValue::new(99)), curr);
|
||||
let result = manager.create(&key, 9).await;
|
||||
let err_msg = result.unwrap_err().to_string();
|
||||
assert!(err_msg.contains("Table already exists, table_id: 99"));
|
||||
|
||||
let value = manager.get(&key).await.unwrap().unwrap();
|
||||
let value = manager.get(key).await.unwrap().unwrap();
|
||||
assert_eq!(value.table_id(), 99);
|
||||
let not_existed = TableNameKey::new("x", "y", "z");
|
||||
assert!(manager.get(¬_existed).await.unwrap().is_none());
|
||||
assert!(manager.get(not_existed).await.unwrap().is_none());
|
||||
|
||||
assert!(manager.remove(&key).await.is_ok());
|
||||
assert!(manager.remove(key).await.is_ok());
|
||||
let kv = backend
|
||||
.get(b"__removed-__table_name/my_catalog/my_schema/my_table")
|
||||
.await
|
||||
@@ -271,9 +310,31 @@ mod tests {
|
||||
let value = TableNameValue::try_from_raw_value(kv.value).unwrap();
|
||||
assert_eq!(value.table_id(), 99);
|
||||
|
||||
let key = TableNameKey::new("my_catalog", "my_schema", "table_1");
|
||||
assert!(manager.rename(key, 1, "table_1_new").await.is_ok());
|
||||
assert!(manager.rename(key, 1, "table_1_new").await.is_ok());
|
||||
|
||||
let result = manager.rename(key, 2, "table_1_new").await;
|
||||
let err_msg = result.unwrap_err().to_string();
|
||||
assert!(err_msg.contains("Table already exists, table_id: 1"));
|
||||
|
||||
let result = manager
|
||||
.rename(
|
||||
TableNameKey::new("my_catalog", "my_schema", "table_2"),
|
||||
22,
|
||||
"table_2_new",
|
||||
)
|
||||
.await;
|
||||
let err_msg = result.unwrap_err().to_string();
|
||||
assert!(err_msg.contains("Failed to rename table, reason: the input table name 'my_catalog.my_schema.table_2' and id '22' not match"));
|
||||
|
||||
let result = manager.rename(not_existed, 1, "zz").await;
|
||||
let err_msg = result.unwrap_err().to_string();
|
||||
assert!(err_msg.contains("Table does not exist, table_name: x.y.z"));
|
||||
|
||||
let tables = manager.tables("my_catalog", "my_schema").await.unwrap();
|
||||
assert_eq!(tables.len(), 3);
|
||||
assert_eq!(tables, vec!["table_1", "table_2", "table_3"]);
|
||||
assert_eq!(tables, vec!["table_1_new", "table_2", "table_3"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -15,17 +15,15 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use snafu::{OptionExt, ResultExt};
|
||||
use snafu::ensure;
|
||||
use store_api::storage::RegionNumber;
|
||||
use table::metadata::TableId;
|
||||
|
||||
use super::TABLE_REGION_KEY_PREFIX;
|
||||
use crate::error::{InvalidCatalogValueSnafu, InvalidTableMetadataSnafu, Result};
|
||||
use crate::helper::{TableGlobalKey, TableGlobalValue};
|
||||
use crate::error::{Result, UnexpectedSnafu};
|
||||
use crate::key::{to_removed_key, TableMetaKey};
|
||||
use crate::kv_backend::KvBackendRef;
|
||||
use crate::rpc::store::{CompareAndPutRequest, MoveValueRequest, PutRequest};
|
||||
use crate::table_name::TableName;
|
||||
use crate::rpc::store::{CompareAndPutRequest, MoveValueRequest};
|
||||
use crate::DatanodeId;
|
||||
|
||||
pub type RegionDistribution = BTreeMap<DatanodeId, Vec<RegionNumber>>;
|
||||
@@ -80,66 +78,30 @@ impl TableRegionManager {
|
||||
.transpose()
|
||||
}
|
||||
|
||||
// TODO(LFC): Remove this method when table metadata refactor is done.
|
||||
pub async fn get_old(&self, table_name: &TableName) -> Result<Option<TableRegionValue>> {
|
||||
let key = TableGlobalKey {
|
||||
catalog_name: table_name.catalog_name.clone(),
|
||||
schema_name: table_name.schema_name.clone(),
|
||||
table_name: table_name.table_name.clone(),
|
||||
}
|
||||
.to_string();
|
||||
let raw_key = key.as_bytes();
|
||||
|
||||
self.kv_backend
|
||||
.get(raw_key)
|
||||
.await?
|
||||
.map(|kv| TableGlobalValue::from_bytes(kv.value()))
|
||||
.transpose()
|
||||
.map(|v| {
|
||||
v.map(|v| TableRegionValue {
|
||||
region_distribution: v.regions_id_map.into_iter().collect(),
|
||||
version: 0,
|
||||
})
|
||||
})
|
||||
.context(InvalidCatalogValueSnafu)
|
||||
}
|
||||
|
||||
// TODO(LFC): Remove this method when table metadata refactor is done.
|
||||
pub async fn put_old(
|
||||
/// Create TableRegion key and value. If the key already exists, check if the value is the same.
|
||||
pub async fn create(
|
||||
&self,
|
||||
table_name: &TableName,
|
||||
region_distribution: RegionDistribution,
|
||||
table_id: TableId,
|
||||
region_distribution: &RegionDistribution,
|
||||
) -> Result<()> {
|
||||
let key = TableGlobalKey {
|
||||
catalog_name: table_name.catalog_name.clone(),
|
||||
schema_name: table_name.schema_name.clone(),
|
||||
table_name: table_name.table_name.clone(),
|
||||
let result = self
|
||||
.compare_and_put(table_id, None, region_distribution.clone())
|
||||
.await?;
|
||||
if let Err(curr) = result {
|
||||
let Some(curr) = curr else {
|
||||
return UnexpectedSnafu {
|
||||
err_msg: format!("compare_and_put expect None but failed with current value None, table_id: {table_id}, region_distribution: {region_distribution:?}"),
|
||||
}.fail()
|
||||
};
|
||||
ensure!(
|
||||
&curr.region_distribution == region_distribution,
|
||||
UnexpectedSnafu {
|
||||
err_msg: format!(
|
||||
"TableRegionValue for table {table_id} is updated before it is created!"
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
.to_string();
|
||||
let raw_key = key.as_bytes();
|
||||
|
||||
let table_info = self
|
||||
.kv_backend
|
||||
.get(raw_key)
|
||||
.await?
|
||||
.map(|kv| TableGlobalValue::from_bytes(kv.value()))
|
||||
.transpose()
|
||||
.context(InvalidCatalogValueSnafu)?
|
||||
.map(|v| v.table_info)
|
||||
.with_context(|| InvalidTableMetadataSnafu {
|
||||
err_msg: format!("table global value for {table_name} is empty"),
|
||||
})?;
|
||||
|
||||
let raw_value = TableGlobalValue {
|
||||
node_id: 0,
|
||||
regions_id_map: region_distribution.into_iter().collect(),
|
||||
table_info,
|
||||
}
|
||||
.as_bytes()
|
||||
.context(InvalidCatalogValueSnafu)?;
|
||||
|
||||
let req = PutRequest::new().with_key(raw_key).with_value(raw_value);
|
||||
self.kv_backend.put(req).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -155,7 +117,7 @@ impl TableRegionManager {
|
||||
table_id: TableId,
|
||||
expect: Option<TableRegionValue>,
|
||||
region_distribution: RegionDistribution,
|
||||
) -> Result<std::result::Result<(), Option<Vec<u8>>>> {
|
||||
) -> Result<std::result::Result<(), Option<TableRegionValue>>> {
|
||||
let key = TableRegionKey::new(table_id);
|
||||
let raw_key = key.as_raw_key();
|
||||
|
||||
@@ -179,16 +141,22 @@ impl TableRegionManager {
|
||||
Ok(if resp.success {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(resp.prev_kv.map(|x| x.value))
|
||||
Err(resp
|
||||
.prev_kv
|
||||
.map(|x| TableRegionValue::try_from_raw_value(x.value))
|
||||
.transpose()?)
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn remove(&self, table_id: TableId) -> Result<()> {
|
||||
pub async fn remove(&self, table_id: TableId) -> Result<Option<TableRegionValue>> {
|
||||
let key = TableRegionKey::new(table_id).as_raw_key();
|
||||
let remove_key = to_removed_key(&String::from_utf8_lossy(&key));
|
||||
let req = MoveValueRequest::new(key, remove_key.as_bytes());
|
||||
self.kv_backend.move_value(req).await?;
|
||||
Ok(())
|
||||
|
||||
let resp = self.kv_backend.move_value(req).await?;
|
||||
resp.0
|
||||
.map(|x| TableRegionValue::try_from_raw_value(x.value))
|
||||
.transpose()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,25 +175,25 @@ mod tests {
|
||||
|
||||
let region_distribution =
|
||||
RegionDistribution::from([(1, vec![1, 2, 3]), (2, vec![4, 5, 6])]);
|
||||
let new_region_distribution =
|
||||
RegionDistribution::from([(1, vec![4, 5, 6]), (2, vec![1, 2, 3])]);
|
||||
|
||||
let result = manager
|
||||
.compare_and_put(1, None, region_distribution.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(result.is_ok());
|
||||
|
||||
let new_region_distribution =
|
||||
RegionDistribution::from([(1, vec![4, 5, 6]), (2, vec![1, 2, 3])]);
|
||||
let curr = manager
|
||||
.compare_and_put(1, None, new_region_distribution.clone())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap_err()
|
||||
.unwrap();
|
||||
let curr = TableRegionValue::try_from_raw_value(curr).unwrap();
|
||||
assert_eq!(
|
||||
curr,
|
||||
TableRegionValue {
|
||||
region_distribution,
|
||||
region_distribution: region_distribution.clone(),
|
||||
version: 0
|
||||
}
|
||||
);
|
||||
@@ -236,6 +204,13 @@ mod tests {
|
||||
.unwrap()
|
||||
.is_ok());
|
||||
|
||||
assert!(manager.create(99, ®ion_distribution).await.is_ok());
|
||||
assert!(manager.create(99, ®ion_distribution).await.is_ok());
|
||||
|
||||
let result = manager.create(99, &new_region_distribution).await;
|
||||
let err_msg = result.unwrap_err().to_string();
|
||||
assert!(err_msg.contains("TableRegionValue for table 99 is updated before it is created!"));
|
||||
|
||||
let value = manager.get(1).await.unwrap().unwrap();
|
||||
assert_eq!(
|
||||
value,
|
||||
@@ -244,9 +219,25 @@ mod tests {
|
||||
version: 1
|
||||
}
|
||||
);
|
||||
let value = manager.get(99).await.unwrap().unwrap();
|
||||
assert_eq!(
|
||||
value,
|
||||
TableRegionValue {
|
||||
region_distribution,
|
||||
version: 0
|
||||
}
|
||||
);
|
||||
assert!(manager.get(2).await.unwrap().is_none());
|
||||
|
||||
assert!(manager.remove(1).await.is_ok());
|
||||
let value = manager.remove(1).await.unwrap().unwrap();
|
||||
assert_eq!(
|
||||
value,
|
||||
TableRegionValue {
|
||||
region_distribution: new_region_distribution.clone(),
|
||||
version: 1
|
||||
}
|
||||
);
|
||||
assert!(manager.remove(123).await.unwrap().is_none());
|
||||
|
||||
let kv = backend
|
||||
.get(b"__removed-__table_region/1")
|
||||
|
||||
@@ -21,6 +21,7 @@ use crate::key::to_removed_key;
|
||||
|
||||
pub const TABLE_ROUTE_PREFIX: &str = "__meta_table_route";
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct TableRouteKey<'a> {
|
||||
pub table_id: TableId,
|
||||
pub catalog_name: &'a str,
|
||||
|
||||
@@ -31,7 +31,6 @@ use crate::table_name::TableName;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct RouteRequest {
|
||||
pub table_names: Vec<TableName>,
|
||||
pub table_ids: Vec<TableId>,
|
||||
}
|
||||
|
||||
@@ -39,7 +38,6 @@ impl From<RouteRequest> for PbRouteRequest {
|
||||
fn from(mut req: RouteRequest) -> Self {
|
||||
Self {
|
||||
header: None,
|
||||
table_names: req.table_names.drain(..).map(Into::into).collect(),
|
||||
table_ids: req.table_ids.drain(..).map(|id| PbTableId { id }).collect(),
|
||||
}
|
||||
}
|
||||
@@ -49,16 +47,9 @@ impl RouteRequest {
|
||||
#[inline]
|
||||
pub fn new(table_id: TableId) -> Self {
|
||||
Self {
|
||||
table_names: vec![],
|
||||
table_ids: vec![table_id],
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn add_table_name(mut self, table_name: TableName) -> Self {
|
||||
self.table_names.push(table_name);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -377,26 +368,14 @@ mod tests {
|
||||
#[test]
|
||||
fn test_route_request_trans() {
|
||||
let req = RouteRequest {
|
||||
table_names: vec![
|
||||
TableName::new("c1", "s1", "t1"),
|
||||
TableName::new("c2", "s2", "t2"),
|
||||
],
|
||||
table_ids: vec![1, 2, 3],
|
||||
table_ids: vec![1, 2],
|
||||
};
|
||||
|
||||
let into_req: PbRouteRequest = req.into();
|
||||
|
||||
assert!(into_req.header.is_none());
|
||||
assert_eq!("c1", into_req.table_names.get(0).unwrap().catalog_name);
|
||||
assert_eq!("s1", into_req.table_names.get(0).unwrap().schema_name);
|
||||
assert_eq!("t1", into_req.table_names.get(0).unwrap().table_name);
|
||||
assert_eq!("c2", into_req.table_names.get(1).unwrap().catalog_name);
|
||||
assert_eq!("s2", into_req.table_names.get(1).unwrap().schema_name);
|
||||
assert_eq!("t2", into_req.table_names.get(1).unwrap().table_name);
|
||||
assert_eq!(
|
||||
(1..=3).map(|id| PbTableId { id }).collect::<Vec<_>>(),
|
||||
into_req.table_ids
|
||||
);
|
||||
assert_eq!(1, into_req.table_ids.get(0).unwrap().id);
|
||||
assert_eq!(2, into_req.table_ids.get(1).unwrap().id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user