Skip to content

Commit

Permalink
feature: metasrv has to be compatible with 20220413-34e89c99e4e356327…
Browse files Browse the repository at this point in the history
…18e9227f6549b3090eb0fb9

Let metasvr be able to load the log-entry format before commit
34e89c9,
in which the `Cmd` format changed.

To solve th compatible issue, an intermedia type is introduced, which is
a super set of the old and new type.
When loading a record, first load it into the superset type, then reduce
to the latest type.

- fix: databendlabs#4890
  • Loading branch information
drmingdrmer committed Apr 17, 2022
1 parent d09955c commit 223e6cf
Show file tree
Hide file tree
Showing 6 changed files with 412 additions and 1 deletion.
161 changes: 160 additions & 1 deletion common/meta/types/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
use std::fmt;

use openraft::NodeId;
use serde::de;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;

use crate::compatibility::cmd_20220413;
use crate::CreateDatabaseReq;
use crate::CreateShareReq;
use crate::CreateTableReq;
Expand All @@ -33,7 +36,7 @@ use crate::UpsertTableOptionReq;

/// A Cmd describes what a user want to do to raft state machine
/// and is the essential part of a raft log.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[derive(Serialize, Debug, Clone, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum Cmd {
/// Increment the sequence number generator specified by `key` and returns the new value.
Expand Down Expand Up @@ -64,6 +67,7 @@ pub enum Cmd {

/// Create a share if absent
CreateShare(CreateShareReq),

DropShare(DropShareReq),

/// Update, remove or insert table options.
Expand Down Expand Up @@ -125,3 +129,158 @@ impl fmt::Display for Cmd {
}
}
}

impl<'de> Deserialize<'de> for Cmd {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de> {
let c: cmd_20220413::Cmd = de::Deserialize::deserialize(deserializer)?;
let latest = match c {
cmd_20220413::Cmd::IncrSeq { key } => Cmd::IncrSeq { key },
cmd_20220413::Cmd::AddNode { node_id, node } => Cmd::AddNode { node_id, node },
cmd_20220413::Cmd::CreateDatabase {
if_not_exists,
tenant,
name,
db_name,
meta,
} => {
if if_not_exists.is_some() {
// latest
Cmd::CreateDatabase(CreateDatabaseReq {
if_not_exists: if_not_exists.unwrap(),
tenant,
db_name: db_name.unwrap(),
meta,
})
} else {
// 20220413
Cmd::CreateDatabase(CreateDatabaseReq {
if_not_exists: false,
tenant,
db_name: name.unwrap(),
meta,
})
}
}
cmd_20220413::Cmd::DropDatabase {
if_exists,
tenant,
name,
db_name,
} => {
if if_exists.is_some() {
// latest
Cmd::DropDatabase(DropDatabaseReq {
if_exists: if_exists.unwrap(),
tenant,
db_name: db_name.unwrap(),
})
} else {
// 20220413
Cmd::DropDatabase(DropDatabaseReq {
if_exists: false,
tenant,
db_name: name.unwrap(),
})
}
}
cmd_20220413::Cmd::CreateTable {
if_not_exists,
tenant,
db_name,
table_name,
table_meta,
} => {
if if_not_exists.is_some() {
// latest
Cmd::CreateTable(CreateTableReq {
if_not_exists: if_not_exists.unwrap(),
tenant,
db_name,
table_name,
table_meta,
})
} else {
// 20220413
Cmd::CreateTable(CreateTableReq {
if_not_exists: false,
tenant,
db_name,
table_name,
table_meta,
})
}
}
cmd_20220413::Cmd::DropTable {
if_exists,
tenant,
db_name,
table_name,
} => {
if if_exists.is_some() {
// latest
Cmd::DropTable(DropTableReq {
if_exists: if_exists.unwrap(),
tenant,
db_name,
table_name,
})
} else {
// 20220413
Cmd::DropTable(DropTableReq {
if_exists: false,
tenant,
db_name,
table_name,
})
}
}
cmd_20220413::Cmd::RenameTable {
if_exists,
tenant,
db_name,
table_name,
new_db_name,
new_table_name,
} => {
if if_exists.is_some() {
// latest
Cmd::RenameTable(RenameTableReq {
if_exists: if_exists.unwrap(),
tenant,
db_name,
table_name,
new_db_name,
new_table_name,
})
} else {
// 20220413
Cmd::RenameTable(RenameTableReq {
if_exists: false,
tenant,
db_name,
table_name,
new_db_name,
new_table_name,
})
}
}
cmd_20220413::Cmd::CreateShare(x) => Cmd::CreateShare(x),
cmd_20220413::Cmd::DropShare(x) => Cmd::DropShare(x),
cmd_20220413::Cmd::UpsertTableOptions(x) => Cmd::UpsertTableOptions(x),
cmd_20220413::Cmd::UpsertKV {
key,
seq,
value,
value_meta,
} => Cmd::UpsertKV {
key,
seq,
value,
value_meta,
},
};

Ok(latest)
}
}
113 changes: 113 additions & 0 deletions common/meta/types/src/compatibility/cmd_20220413.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright 2021 Datafuse Labs.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use openraft::NodeId;
use serde::Deserialize;
use serde::Serialize;

use crate::CreateShareReq;
use crate::DatabaseMeta;
use crate::DropShareReq;
use crate::KVMeta;
use crate::MatchSeq;
use crate::Node;
use crate::Operation;
use crate::TableMeta;
use crate::UpsertTableOptionReq;

/// Compatible with changes made in 34e89c99e4e35632718e9227f6549b3090eb0fb9 on 20220413
/// This struct can deserialize json built by the binary before and after this commit.
///
/// - 20220413: In this commit:
/// - It replaced variant struct with standalone struct.
/// - The standalone struct has a `if_not_exists` or `if_not_exists`.
///
/// Compatibility:
/// - Json layout for variant struct and standalone struct is the same.
/// - `if_exist` and `if_not_exist` only affects client response, but not meta data.
/// Thus it is safe to give it a default value.
///
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum Cmd {
IncrSeq {
key: String,
},

AddNode {
node_id: NodeId,
node: Node,
},

CreateDatabase {
// latest add
if_not_exists: Option<bool>,
tenant: String,
// 20220413
name: Option<String>,
// latest add
db_name: Option<String>,
meta: DatabaseMeta,
},

DropDatabase {
// latest add
if_exists: Option<bool>,
tenant: String,
// 20220413
name: Option<String>,
// latest add
db_name: Option<String>,
},

CreateTable {
// latest add
if_not_exists: Option<bool>,
tenant: String,
db_name: String,
table_name: String,
table_meta: TableMeta,
},

DropTable {
// latest add
if_exists: Option<bool>,
tenant: String,
db_name: String,
table_name: String,
},

RenameTable {
// latest add
if_exists: Option<bool>,
tenant: String,
db_name: String,
table_name: String,
new_db_name: String,
new_table_name: String,
},
// latest add
CreateShare(CreateShareReq),
// latest add
DropShare(DropShareReq),

UpsertTableOptions(UpsertTableOptionReq),

UpsertKV {
key: String,
seq: MatchSeq,
value: Operation<Vec<u8>>,
value_meta: Option<KVMeta>,
},
}
86 changes: 86 additions & 0 deletions common/meta/types/src/compatibility/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2021 Datafuse Labs.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Provides backward compatibility.
//!
//! Changed meta data types is loaded into a type that is compatible with all old versions.
//! And then reduce the compatible version to the latest version.
//!
//! To guarantee compatibility:
//! - Field type must not change: Changing `x: String` to `x: i64` makes compatibility impassible to achieve.
//! - Only add or remove fields.
//!
//! E.g. `A` is defined as:
//! ```ignore
//! #[derive(Serialize, Deserialize)]
//! struct A {
//! i: u64,
//! }
//! ```
//!
//! An upgrade may introduce another field `j`, and remove `i`.
//! The upgraded message `B` will be:
//!```ignore
//! #[derive(Serialize)]
//! struct Foo {
//! j: u64,
//! }
//!```
//!
//! To be compatible with `A` and `B`, the max compatible `C` should be:
//!```ignore
//! #[derive(Serialize, Deserialize)]
//! struct Compatible {
//! i: Option<u64>,
//! j: Option<u64>,
//! }
//!```
//!
//! This way `Compatible` is able to load both `{"i": 1}` or `{"j": 2}`.
//! The complete example is:
//! ```ignore
//! #[derive(Debug, Serialize, Deserialize)]
//! struct A {
//! pub i: u64,
//! }
//!
//! #[derive(Debug, Serialize)]
//! struct B {
//! pub j: u64,
//! }
//!
//! #[derive(Debug, Serialize, Deserialize)]
//! struct Compatible {
//! pub i: Option<u64>,
//! pub j: Option<u64>,
//! }
//!
//! impl<'de> Deserialize<'de> for B {
//! fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
//! where
//! D: Deserializer<'de>,
//! {
//! let c: Compatible = de::Deserialize::deserialize(deserializer)?;
//!
//! if c.i.is_some() {
//! println!("loaded from serialized A, convert to B");
//! Ok(B { j: c.i.unwrap() })
//! } else {
//! println!("loaded from serialized B");
//! Ok(B { j: c.j.unwrap() })
//! }
//! }
//! }
//! ```
pub(crate) mod cmd_20220413;
1 change: 1 addition & 0 deletions common/meta/types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ mod user_quota;
mod user_setting;
mod user_stage;

pub(crate) mod compatibility;
pub mod error_context;
mod principal_identity;
mod share;
Expand Down
Loading

0 comments on commit 223e6cf

Please sign in to comment.