Skip to content
Snippets Groups Projects
Commit c735dab2 authored by Egor Ivkov's avatar Egor Ivkov
Browse files

feat: make predicate args optional in cas

parent 623b8b6a
No related branches found
No related tags found
1 merge request!576feat: make predicate args optional in cas
......@@ -637,21 +637,26 @@ pub(crate) fn setup(args: &args::Run) {
&l,
"cas",
indoc! {"
pico.cas(dml, predicate)
pico.cas(dml, [predicate])
========================
Performs a clusterwide compare and swap operation.
E.g. it checks the `predicate` on leader and if no conflicting entries were found
appends the `op` to the raft log and returns its index (number).
appends the `op` to the raft log and returns its index (number). If predicate
is not supplied, it will be auto generated with `index` and `term` taken from the
current instance and with empty `ranges`.
# Params
1. dml - table. See pico.help(\"Dml\")
2. predicate - table. See pico.help(\"Predicate\")
"},
tlua::function2(
|op: op::DmlInLua, predicate: rpc::cas::Predicate| -> traft::Result<RaftIndex> {
|op: op::DmlInLua,
predicate: Option<rpc::cas::PredicateInLua>|
-> traft::Result<RaftIndex> {
let op = op::Dml::from_lua_args(op).map_err(traft::error::Error::other)?;
let predicate = rpc::cas::Predicate::from_lua_args(predicate.unwrap_or_default())?;
let (index, _) = compare_and_swap(op.into(), predicate)?;
Ok(index)
},
......@@ -676,10 +681,15 @@ pub(crate) fn setup(args: &args::Run) {
"Predicate",
indoc! {"
Predicate that will be checked by the leader, before accepting the proposed op.
Has these fields:
- index (number)
- term (number)
- ranges (table)
- index (optional, number)
- term (optional, number)
- ranges (optional, table)
If some fields are not supplied, they will be autogenerated. `index` and `term` taken from the
raft state on the instance which sends this operation and `ranges` left as an empty
vector.
"},
);
luamod_set(
......
use crate::storage::Clusterwide;
use crate::storage::ClusterwideSpaceId;
use crate::tlog;
use crate::traft;
use crate::traft::error::Error as TraftError;
use crate::traft::node;
use crate::traft::op::{Ddl, Dml, Op};
......@@ -215,8 +216,22 @@ pub enum Error {
KeyTypeMismatch(#[from] TntError),
}
/// Represents a lua table describing a [`Predicate`].
///
/// This is only used to parse lua arguments from lua api functions such as
/// `pico.cas`.
#[derive(Clone, Debug, Default, ::serde::Serialize, ::serde::Deserialize, tlua::LuaRead)]
pub struct PredicateInLua {
/// CaS sender's current raft index.
pub index: Option<RaftIndex>,
/// CaS sender's current raft term.
pub term: Option<RaftTerm>,
/// Range that the CaS sender have read and expects it to be unmodified.
pub ranges: Option<Vec<Range>>,
}
/// Predicate that will be checked by the leader, before accepting the proposed `op`.
#[derive(Clone, Debug, ::serde::Serialize, ::serde::Deserialize, tlua::LuaRead)]
#[derive(Clone, Debug, ::serde::Serialize, ::serde::Deserialize)]
pub struct Predicate {
/// CaS sender's current raft index.
pub index: RaftIndex,
......@@ -227,6 +242,27 @@ pub struct Predicate {
}
impl Predicate {
pub fn from_lua_args(predicate: PredicateInLua) -> traft::Result<Self> {
let node = traft::node::global()?;
let (index, term) = if let Some(index) = predicate.index {
if let Some(term) = predicate.term {
(index, term)
} else {
let term = raft::Storage::term(&node.raft_storage, index)?;
(index, term)
}
} else {
let index = node.get_index();
let term = raft::Storage::term(&node.raft_storage, index)?;
(index, term)
};
Ok(Self {
index,
term,
ranges: predicate.ranges.unwrap_or_default(),
})
}
/// Checks if `entry_op` changes anything within the ranges specified in the predicate.
pub fn check_entry(
&self,
......
......@@ -607,6 +607,21 @@ class Instance:
return self.call("pico.raft_compact_log", 0)
def space_id(self, space: str | int) -> int:
"""
Get space id by space name.
If id is supplied instead it is just returned back.
This method is useful in functions which can take both id and space name.
"""
match space:
case int():
return space
case str():
return self.eval("return box.space[...].id", space)
case _:
raise TypeError("space must be str or int")
def cas(
self,
dml_kind: Literal["insert", "replace", "delete"],
......@@ -614,7 +629,7 @@ class Instance:
tuple: Tuple | List,
index: int | None = None,
term: int | None = None,
range: CasRange | None = None, # TODO better types for bounds
range: CasRange | None = None,
) -> int:
"""
Performs a clusterwide compare and swap operation.
......@@ -622,13 +637,6 @@ class Instance:
E.g. it checks the `predicate` on leader and if no conflicting entries were found
appends the `op` to the raft log and returns its index.
`range` is a tuple of two dictionaries: (key_min, key_max). Each dictionary
is of the following structure:
{
"kind": "included" | "excluded" | "unbounded",
"value": int | None
}
ASSUMPTIONS
It is assumed that this operation is called on leader.
Failing to do so will result in an error.
......@@ -639,14 +647,7 @@ class Instance:
elif term is None:
term = self.raft_term_by_index(index)
space_id = None
match space:
case int():
space_id = space
case str():
space_id = self.eval("return box.space[...].id", space)
case _:
raise TypeError("space must be str or int")
space_id = self.space_id(space)
predicate_range = None
if range is not None:
......@@ -1077,7 +1078,7 @@ class Cluster:
tuple: Tuple | List,
index: int | None = None,
term: int | None = None,
range: CasRange | None = None, # TODO better types
range: CasRange | None = None,
# If specified send CaS through this instance
instance: Instance | None = None,
) -> int:
......@@ -1088,21 +1089,9 @@ class Cluster:
appends the `op` to the raft log and returns its index.
Calling this operation will route CaS request to a leader.
`range` is a tuple of two dictionaries: (key_min, key_max). Each dictionary
is of the following structure:
{
"kind": "included" | "excluded" | "unbounded",
"value": int # skip if `None`
}
"""
if instance is None:
instance = self.instances[0]
if index is None:
index = instance.raft_read_index()
term = instance.raft_term_by_index(index)
elif term is None:
term = instance.raft_term_by_index(index)
predicate_range = None
if range is not None:
......@@ -1115,7 +1104,7 @@ class Cluster:
predicate = dict(
index=index,
term=term,
ranges=[predicate_range] if predicate_range is not None else [],
ranges=predicate_range,
)
if dml_kind in ["insert", "replace", "delete"]:
dml = dict(
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment