From 78097ba693c3f47910a10a46e28f7f8217d871fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=82=8E=E6=B3=BC?= Date: Sun, 17 Apr 2022 22:46:16 +0800 Subject: [PATCH] feature: metasrv has to be compatible with 20220413-34e89c99e4e35632718e9227f6549b3090eb0fb9 Let metasvr be able to load the log-entry format before commit 34e89c99e4e35632718e9227f6549b3090eb0fb9, 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: #4890 --- common/meta/types/src/cmd.rs | 161 +++++++++++++++++- .../types/src/compatibility/cmd_20220413.rs | 113 ++++++++++++ common/meta/types/src/compatibility/mod.rs | 86 ++++++++++ common/meta/types/src/lib.rs | 1 + common/meta/types/tests/it/compatible.rs | 51 ++++++ common/meta/types/tests/it/main.rs | 1 + 6 files changed, 412 insertions(+), 1 deletion(-) create mode 100644 common/meta/types/src/compatibility/cmd_20220413.rs create mode 100644 common/meta/types/src/compatibility/mod.rs create mode 100644 common/meta/types/tests/it/compatible.rs diff --git a/common/meta/types/src/cmd.rs b/common/meta/types/src/cmd.rs index 82e3da24a8b7e..9b6000228d616 100644 --- a/common/meta/types/src/cmd.rs +++ b/common/meta/types/src/cmd.rs @@ -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; @@ -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. @@ -64,6 +67,7 @@ pub enum Cmd { /// Create a share if absent CreateShare(CreateShareReq), + DropShare(DropShareReq), /// Update, remove or insert table options. @@ -125,3 +129,158 @@ impl fmt::Display for Cmd { } } } + +impl<'de> Deserialize<'de> for Cmd { + fn deserialize(deserializer: D) -> Result + 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 let Some(x) = if_not_exists { + // latest + Cmd::CreateDatabase(CreateDatabaseReq { + if_not_exists: x, + 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 let Some(x) = if_exists { + // latest + Cmd::DropDatabase(DropDatabaseReq { + if_exists: x, + 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 let Some(x) = if_not_exists { + // latest + Cmd::CreateTable(CreateTableReq { + if_not_exists: x, + 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 let Some(x) = if_exists { + // latest + Cmd::DropTable(DropTableReq { + if_exists: x, + 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 let Some(x) = if_exists { + // latest + Cmd::RenameTable(RenameTableReq { + if_exists: x, + 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) + } +} diff --git a/common/meta/types/src/compatibility/cmd_20220413.rs b/common/meta/types/src/compatibility/cmd_20220413.rs new file mode 100644 index 0000000000000..e28c0004e58a3 --- /dev/null +++ b/common/meta/types/src/compatibility/cmd_20220413.rs @@ -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, + tenant: String, + // 20220413 + name: Option, + // latest add + db_name: Option, + meta: DatabaseMeta, + }, + + DropDatabase { + // latest add + if_exists: Option, + tenant: String, + // 20220413 + name: Option, + // latest add + db_name: Option, + }, + + CreateTable { + // latest add + if_not_exists: Option, + tenant: String, + db_name: String, + table_name: String, + table_meta: TableMeta, + }, + + DropTable { + // latest add + if_exists: Option, + tenant: String, + db_name: String, + table_name: String, + }, + + RenameTable { + // latest add + if_exists: Option, + 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>, + value_meta: Option, + }, +} diff --git a/common/meta/types/src/compatibility/mod.rs b/common/meta/types/src/compatibility/mod.rs new file mode 100644 index 0000000000000..afa28c17f6b56 --- /dev/null +++ b/common/meta/types/src/compatibility/mod.rs @@ -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, +//! j: Option, +//! } +//!``` +//! +//! 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, +//! pub j: Option, +//! } +//! +//! impl<'de> Deserialize<'de> for B { +//! fn deserialize(deserializer: D) -> Result +//! 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; diff --git a/common/meta/types/src/lib.rs b/common/meta/types/src/lib.rs index 59f1d742f9ff3..c5e511f5eade7 100644 --- a/common/meta/types/src/lib.rs +++ b/common/meta/types/src/lib.rs @@ -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; diff --git a/common/meta/types/tests/it/compatible.rs b/common/meta/types/tests/it/compatible.rs new file mode 100644 index 0000000000000..66f52c9b07c3c --- /dev/null +++ b/common/meta/types/tests/it/compatible.rs @@ -0,0 +1,51 @@ +// 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 common_meta_types::LogEntry; +use openraft::raft::Entry; + +#[test] +fn test_load_entry_compatibility() -> anyhow::Result<()> { + let entries_20220413 = vec![ + r#"{"log_id":{"term":1,"index":2},"payload":{"Normal":{"txid":null,"cmd":{"AddNode":{"node_id":1,"node":{"name":"","endpoint":{"addr":"localhost","port":28103}}}}}}}"#, + r#"{"log_id":{"term":1,"index":9},"payload":{"Normal":{"txid":null,"cmd":{"CreateDatabase":{"tenant":"test_tenant","name":"default","meta":{"engine":"","engine_options":{},"options":{},"created_on":"2022-02-22T01:51:06.980129Z"}}}}}}"#, + r#"{"log_id":{"term":1,"index":71},"payload":{"Normal":{"txid":null,"cmd":{"DropDatabase":{"tenant":"test_tenant","name":"db1"}}}}}"#, + r#"{"log_id":{"term":1,"index":15},"payload":{"Normal":{"txid":null,"cmd":{"CreateTable":{"tenant":"test_tenant","db_name":"default","table_name":"tbl_01_0002","table_meta":{"schema":{"fields":[{"name":"a","default_expr":null,"data_type":{"type":"NullableType","inner":{"type":"Int32Type"},"name":"Nullable(Int32)"}}],"metadata":{}},"engine":"FUSE","engine_options":{},"options":{},"created_on":"2022-02-22T01:51:11.839689Z"}}}}}}"#, + r#"{"log_id":{"term":1,"index":18},"payload":{"Normal":{"txid":null,"cmd":{"DropTable":{"tenant":"test_tenant","db_name":"default","table_name":"tbl_01_0002"}}}}}"#, + r#"{"log_id":{"term":1,"index":190},"payload":{"Normal":{"txid":null,"cmd":{"RenameTable":{"tenant":"test_tenant","db_name":"default","table_name":"05_0003_at_t0","new_db_name":"default","new_table_name":"05_0003_at_t1"}}}}}"#, + r#"{"log_id":{"term":1,"index":16},"payload":{"Normal":{"txid":null,"cmd":{"UpsertTableOptions":{"table_id":1,"seq":{"Exact":1},"options":{"SNAPSHOT_LOC":"_ss/08b05df6d9264a419f402fc6e1c61b05"}}}}}}"#, + r#"{"log_id":{"term":1,"index":68},"payload":{"Normal":{"txid":null,"cmd":{"UpsertKV":{"key":"__fd_clusters/test_tenant/test_cluster/databend_query/XgXvK4QuPpih6EeHTHYeO6","seq":{"GE":1},"value":"AsIs","value_meta":{"expire_at":1645494749}}}}}}"#, + ]; + + for s in entries_20220413.iter() { + let _ent: Entry = serde_json::from_str(s)?; + } + + let entries_latest = vec![ + r#"{"log_id":{"term":1,"index":2},"payload":{"Normal":{"txid":null,"cmd":{"AddNode":{"node_id":1,"node":{"name":"1","endpoint":{"addr":"localhost","port":28103}}}}}}}"#, + r#"{"log_id":{"term":1,"index":9},"payload":{"Normal":{"txid":null,"cmd":{"CreateDatabase":{"if_not_exists":true,"tenant":"test_tenant","db_name":"default","meta":{"engine":"","engine_options":{},"options":{},"created_on":"2022-04-15T05:24:34.324244Z"}}}}}}"#, + r#"{"log_id":{"term":1,"index":80},"payload":{"Normal":{"txid":null,"cmd":{"DropDatabase":{"if_exists":true,"tenant":"test_tenant","db_name":"db1"}}}}}"#, + r#"{"log_id":{"term":1,"index":15},"payload":{"Normal":{"txid":null,"cmd":{"CreateTable":{"if_not_exists":false,"tenant":"test_tenant","db_name":"default","table_name":"tbl_01_0002","table_meta":{"schema":{"fields":[{"name":"a","default_expr":null,"data_type":{"type":"Int32Type"}}],"metadata":{}},"engine":"FUSE","engine_options":{},"options":{"database_id":"1"},"created_on":"2022-04-15T05:24:39.362029Z"}}}}}}"#, + r#"{"log_id":{"term":1,"index":18},"payload":{"Normal":{"txid":null,"cmd":{"DropTable":{"if_exists":false,"tenant":"test_tenant","db_name":"default","table_name":"tbl_01_0002"}}}}}"#, + r#"{"log_id":{"term":1,"index":190},"payload":{"Normal":{"txid":null,"cmd":{"RenameTable":{"if_exists":false,"tenant":"test_tenant","db_name":"default","table_name":"05_0003_at_t0","new_db_name":"default","new_table_name":"05_0003_at_t1"}}}}}"#, + r#"{"log_id":{"term":1,"index":210},"payload":{"Normal":{"txid":null,"cmd":{"UpsertTableOptions":{"table_id":65,"seq":{"Exact":83},"options":{"snapshot_location":"1/65/_ss/203f980ccce948cb9904bea1649f9e44_v1.json"}}}}}}"#, + r#"{"log_id":{"term":1,"index":10},"payload":{"Normal":{"txid":null,"cmd":{"UpsertKV":{"key":"__fd_clusters/test_tenant/test_cluster/databend_query/x76ukR5f3LiW2WVeSRUjr3","seq":{"Exact":0},"value":{"Update":[123,34,105,100,34,58,34,120,55,54,117,107,82,53,102,51,76,105,87,50,87,86,101,83,82,85,106,114,51,34,44,34,99,112,117,95,110,117,109,115,34,58,49,48,44,34,118,101,114,115,105,111,110,34,58,48,44,34,102,108,105,103,104,116,95,97,100,100,114,101,115,115,34,58,34,48,46,48,46,48,46,48,58,57,48,57,49,34,125]},"value_meta":{"expire_at":1650000334}}}}}}"#, + ]; + + for s in entries_latest.iter() { + let _ent: Entry = serde_json::from_str(s)?; + } + + Ok(()) +} diff --git a/common/meta/types/tests/it/main.rs b/common/meta/types/tests/it/main.rs index 9d18974cddc77..f2d1922d87870 100644 --- a/common/meta/types/tests/it/main.rs +++ b/common/meta/types/tests/it/main.rs @@ -13,6 +13,7 @@ // limitations under the License. mod cluster; +mod compatible; mod match_seq; mod user_defined_function; mod user_grant;