From a0cd0c89ef89b1658fe06fb7fcfccba96a3ca6a9 Mon Sep 17 00:00:00 2001 From: Georgy Moshkin <gmoshkin@picodata.io> Date: Fri, 16 Sep 2022 17:33:55 +0300 Subject: [PATCH] refactor(storage): type-safe access to single fields of peer Every field of Peer now has a corresponding unit struct defined in the `peer_field` module, which implements the `PeerFieldDef` trait which in turn contains information about the field's name, tarantool type and rust type. This information currently is used in the `field_by_raft_id` method and is generated by the `define_peer_fields` macro, which also automates the generation of the format (`peer_format` function) for the "raft_group" space. --- src/traft/error.rs | 4 ++ src/traft/storage.rs | 156 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 127 insertions(+), 33 deletions(-) diff --git a/src/traft/error.rs b/src/traft/error.rs index a25767444e..4dbf27263d 100644 --- a/src/traft/error.rs +++ b/src/traft/error.rs @@ -24,6 +24,10 @@ pub enum Error { }, #[error("error during execution of lua code: {0}")] Lua(#[from] LuaError), + #[error("{0}")] + Tarantool(#[from] ::tarantool::error::Error), + #[error("peer with id {0} not found")] + NoPeerWithRaftId(RaftId), #[error("other error")] Other(Box<dyn std::error::Error>), } diff --git a/src/traft/storage.rs b/src/traft/storage.rs index a9a2b49e0f..33923f9ba6 100644 --- a/src/traft/storage.rs +++ b/src/traft/storage.rs @@ -1,12 +1,13 @@ use ::raft::StorageError; use ::raft::INVALID_ID; use ::tarantool::index::{Index, IteratorType}; -use ::tarantool::space::Space; +use ::tarantool::space::{FieldType, Space}; use ::tarantool::tuple::{DecodeOwned, ToTupleBuffer, Tuple}; use thiserror::Error; use crate::define_str_enum; use crate::traft; +use crate::traft::error::Error as TraftError; use crate::traft::RaftId; use crate::traft::RaftIndex; @@ -54,6 +55,7 @@ pub struct UnknownStateKey(pub String); // Error //////////////////////////////////////////////////////////////////////////////// +// TODO: remove this type, use traft::error::Error instead #[allow(clippy::enum_variant_names)] #[derive(Debug, Error)] enum Error { @@ -230,44 +232,32 @@ impl Peers { const INDEX_REPLICASET_ID: &'static str = "replicaset_id"; pub fn new() -> tarantool::Result<Self> { - use tarantool::space::Field; - use PeerField::*; - let space_peers = Space::builder(Self::SPACE_NAME) .is_local(true) .is_temporary(false) - .field(Field::string(InstanceId.as_str())) - .field(Field::string(InstanceUuid.as_str())) - .field(Field::unsigned(RaftId.as_str())) - .field(Field::string(PeerAddress.as_str())) - .field(Field::string(ReplicasetId.as_str())) - .field(Field::string(ReplicasetUuid.as_str())) - .field(Field::unsigned(CommitIndex.as_str())) - .field(Field::string(Grade.as_str())) - .field(Field::string(TargetGrade.as_str())) - .field(Field::map(FailureDomain.as_str())) + .format(peer_format()) .if_not_exists(true) .create()?; let index_instance_id = space_peers .index_builder(Self::INDEX_INSTANCE_ID) .unique(true) - .part(InstanceId.as_str()) + .part(peer_field::InstanceId) .if_not_exists(true) .create()?; let index_raft_id = space_peers .index_builder(Self::INDEX_RAFT_ID) .unique(true) - .part(RaftId.as_str()) + .part(peer_field::RaftId) .if_not_exists(true) .create()?; let index_replicaset_id = space_peers .index_builder(Self::INDEX_REPLICASET_ID) .unique(false) - .part(ReplicasetId.as_str()) - .part(CommitIndex.as_str()) + .part(peer_field::ReplicasetId) + .part(peer_field::CommitIndex) .if_not_exists(true) .create()?; @@ -305,6 +295,26 @@ impl Peers { } } + /// Find a peer by `raft_id` and return a single field specified by `F` + /// (see `PeerFieldDef` & `peer_field` module). + #[inline] + pub fn field_by_raft_id<F>(&self, raft_id: RaftId) -> Result<F::Type, TraftError> + where + F: PeerFieldDef, + { + if raft_id == INVALID_ID { + unreachable!("peer_by_raft_id called with invalid id ({})", INVALID_ID); + } + + let res = self + .index_raft_id + .get(&[raft_id])? + .ok_or(TraftError::NoPeerWithRaftId(raft_id))? + .get(F::NAME) + .expect("peer fields are not nullable"); + Ok(res) + } + #[inline] pub fn peer_by_instance_id(&self, instance_id: &str) -> tarantool::Result<Option<traft::Peer>> { let tuple = self.index_instance_id.get(&(instance_id,))?; @@ -356,22 +366,81 @@ impl Peers { // PeerField //////////////////////////////////////////////////////////////////////////////// -crate::define_str_enum! { - /// An enumeration of raft_space field names - pub enum PeerField { - InstanceId = "instance_id", - InstanceUuid = "instance_uuid", - RaftId = "raft_id", - PeerAddress = "peer_address", - ReplicasetId = "replicaset_id", - ReplicasetUuid = "replicaset_uuid", - CommitIndex = "commit_index", - Grade = "grade", - TargetGrade = "target_grade", - FailureDomain = "failure_domain", - } +macro_rules! define_peer_fields { + ($($field:ident: $ty:ty = ($name:literal, $tt_ty:path))+) => { + crate::define_str_enum! { + /// An enumeration of raft_space field names + pub enum PeerField { + $($field = $name,)+ + } - FromStr::Err = UnknownPeerField; + FromStr::Err = UnknownPeerField; + } + + pub mod peer_field { + use super::*; + $( + /// Helper struct that represents + #[doc = stringify!($name)] + /// field of `Peer`. + /// + /// It's rust type is + #[doc = concat!("`", stringify!($ty), "`")] + /// and it's tarantool type is + #[doc = concat!("`", stringify!($tt_ty), "`")] + pub struct $field; + + impl PeerFieldDef for $field { + type Type = $ty; + const NAME: &'static str = $name; + const TYPE: ::tarantool::space::FieldType = $tt_ty; + } + + impl From<$field> for ::tarantool::index::Part { + #[inline(always)] + fn from(_: $field) -> ::tarantool::index::Part { + $field::NAME.into() + } + } + + impl From<$field> for ::tarantool::space::Field { + #[inline(always)] + fn from(_: $field) -> ::tarantool::space::Field { + ($field::NAME, $field::TYPE).into() + } + } + + impl ::tarantool::tuple::TupleIndex for $field { + #[inline(always)] + fn get_field<'a, T>(self, tuple: &'a Tuple) -> ::tarantool::Result<Option<T>> + where + T: ::tarantool::tuple::Decode<'a>, + { + Self::NAME.get_field(tuple) + } + } + )+ + } + + fn peer_format() -> Vec<::tarantool::space::Field> { + vec![ + $( ::tarantool::space::Field::from(($name, $tt_ty)), )+ + ] + } + }; +} + +define_peer_fields! { + InstanceId : String = ("instance_id", FieldType::String) + InstanceUuid : String = ("instance_uuid", FieldType::String) + RaftId : traft::RaftId = ("raft_id", FieldType::Unsigned) + PeerAddress : String = ("peer_address", FieldType::String) + ReplicasetId : String = ("replicaset_id", FieldType::String) + ReplicasetUuid : String = ("replicaset_uuid", FieldType::String) + CommitIndex : RaftIndex = ("commit_index", FieldType::Unsigned) + Grade : traft::Grade = ("grade", FieldType::String) + TargetGrade : traft::TargetGrade = ("target_grade", FieldType::String) + FailureDomain : traft::FailureDomain = ("failure_domain", FieldType::Map) } #[derive(Error, Debug)] @@ -387,6 +456,27 @@ impl tarantool::tuple::TupleIndex for PeerField { } } +/// A helper trait for type-safe and efficient access to a Peer's fields +/// without deserializing the whole tuple. +/// +/// This trait contains information needed to define and use a given tuple field. +pub trait PeerFieldDef { + /// Rust type of the field. + /// + /// Used when decoding the field. + type Type: tarantool::tuple::DecodeOwned; + + /// Field name in the format of the space. + /// + /// Used for accessing the field. + const NAME: &'static str; + + /// Tarantool type of the field in the format of the space. + /// + /// Used for format definition. + const TYPE: tarantool::space::FieldType; +} + //////////////////////////////////////////////////////////////////////////////// // tests //////////////////////////////////////////////////////////////////////////////// -- GitLab