From b2675d0713de45d99bee986998012df1c1cfbf07 Mon Sep 17 00:00:00 2001 From: Andrey Kononov Date: Tue, 5 Sep 2023 01:04:13 +0400 Subject: [PATCH] `DmoOperation` wrapper --- CHANGELOG.md | 6 +- examples/schema.rs | 19 +- src/client/dmo/mod.rs | 4 + src/client/dmo/operation.rs | 173 ++++++++++++++++++ .../{dmo_response.rs => dmo/response.rs} | 0 src/client/executor_ext.rs | 6 +- src/client/mod.rs | 11 +- src/client/sql/mod.rs | 4 + .../prepared_statement.rs} | 0 .../{sql_response.rs => sql/response.rs} | 0 src/lib.rs | 2 +- src/tuple.rs | 37 +++- 12 files changed, 232 insertions(+), 30 deletions(-) create mode 100644 src/client/dmo/mod.rs create mode 100644 src/client/dmo/operation.rs rename src/client/{dmo_response.rs => dmo/response.rs} (100%) create mode 100644 src/client/sql/mod.rs rename src/client/{prepared_sql_statement.rs => sql/prepared_statement.rs} (100%) rename src/client/{sql_response.rs => sql/response.rs} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74b34c4..836d5e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.0.8] - 2023-09-04 +## [Unreleased] - 2023-09-04 ### Added - - Data-manipulation operations (insert, update, upsert, replace, delete) now return `DmoResponse` with row, returned by operation (#7). + - Data-manipulation operations (insert, update, upsert, replace, delete) now return `DmoResponse` with row, returned by operation ([#7](https://github.com/Flowneee/tarantool-rs/issues/7)); + - `TupleElement` type, which allow to write type into `Tuple` without having `serde::Serialize` implemented for it; + - `DmoOperation` for constructing operations in `update` and `upsert` calls. ### Changed - `TupleResponse` renamed to `CallResponse`. diff --git a/examples/schema.rs b/examples/schema.rs index 8530f65..672eb63 100644 --- a/examples/schema.rs +++ b/examples/schema.rs @@ -1,5 +1,4 @@ -use rmpv::Value; -use tarantool_rs::{Connection, Executor, ExecutorExt, IteratorType}; +use tarantool_rs::{Connection, DmoOperation, Executor, ExecutorExt, IteratorType}; use tracing::info; #[tokio::main] @@ -18,17 +17,21 @@ async fn main() -> Result<(), anyhow::Error> { .select::<(i64, String), _>(None, None, Some(IteratorType::All), ()) .await? ); - info!("UPSERT: {:?}", space.upsert((0, "Name"), ("=",)).await?); + info!( + "UPSERT: {:?}", + space + .upsert( + (0, "Name"), + DmoOperation::string_splice("name", 2, 2, "!!".into()), + ) + .await? + ); info!( "UPDATE: {:?}", space .update( (0,), - (rmpv::Value::Array(vec![ - "=".into(), - 1.into(), - "Second".into() - ]),), + (DmoOperation::string_splice("name", 2, 2, "!!".into()),) ) .await? ); diff --git a/src/client/dmo/mod.rs b/src/client/dmo/mod.rs new file mode 100644 index 0000000..e48e432 --- /dev/null +++ b/src/client/dmo/mod.rs @@ -0,0 +1,4 @@ +pub use self::{operation::DmoOperation, response::DmoResponse}; + +mod operation; +mod response; diff --git a/src/client/dmo/operation.rs b/src/client/dmo/operation.rs new file mode 100644 index 0000000..d581b00 --- /dev/null +++ b/src/client/dmo/operation.rs @@ -0,0 +1,173 @@ +use std::io::Write; + +use rmpv::ValueRef; + +use crate::{errors::EncodingError, Tuple, TupleElement}; + +/// Key of index in operation. +/// +/// Can be string, unsigned or signed number ([docs](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_space/update/#box-space-update)). +#[derive(Debug)] +pub struct DmoOperationFieldKey<'a>(ValueRef<'a>); + +impl<'a> From<&'a str> for DmoOperationFieldKey<'a> { + fn from(value: &'a str) -> Self { + Self(value.into()) + } +} + +impl<'a> From for DmoOperationFieldKey<'a> { + fn from(value: u32) -> Self { + Self(value.into()) + } +} + +impl<'a> From for DmoOperationFieldKey<'a> { + fn from(value: i32) -> Self { + Self(value.into()) + } +} + +// TODO: docs and doctests +/// Operation in `upsert` or `update` request. +#[derive(Debug)] +pub struct DmoOperation<'a> { + // TODO: id support + // TODO: negative id support + operation: &'static str, + field_name: ValueRef<'a>, + args: Args<'a>, +} + +impl<'a> DmoOperation<'a> { + fn new( + operation: &'static str, + field_name: impl Into>, + args: Args<'a>, + ) -> Self { + Self { + operation, + field_name: field_name.into().0, + args, + } + } + + pub fn add( + field_name: impl Into>, + value: impl Into>, + ) -> Self { + Self::new(ops::ADD, field_name, Args::One(value.into())) + } + + pub fn sub( + field_name: impl Into>, + value: impl Into>, + ) -> Self { + Self::new(ops::SUB, field_name, Args::One(value.into())) + } + + pub fn and( + field_name: impl Into>, + value: impl Into>, + ) -> Self { + Self::new(ops::AND, field_name, Args::One(value.into())) + } + + pub fn or( + field_name: impl Into>, + value: impl Into>, + ) -> Self { + Self::new(ops::OR, field_name, Args::One(value.into())) + } + + pub fn xor( + field_name: impl Into>, + value: impl Into>, + ) -> Self { + Self::new(ops::XOR, field_name, Args::One(value.into())) + } + + pub fn string_splice( + field_name: impl Into>, + from: usize, + len: usize, + value: &'a str, + ) -> Self { + Self::new( + ops::STRING_SPLICE, + field_name, + Args::Three(from.into(), len.into(), value.into()), + ) + } + + pub fn insert( + field_name: impl Into>, + value: impl Into>, + ) -> Self { + Self::new(ops::INSERT, field_name, Args::One(value.into())) + } + + pub fn assign( + field_name: impl Into>, + value: impl Into>, + ) -> Self { + Self::new(ops::ASSIGN, field_name, Args::One(value.into())) + } + + pub fn delete(field_name: impl Into>) -> Self { + Self::new(ops::DEL, field_name, Args::None) + } +} + +impl<'a> TupleElement for DmoOperation<'a> { + fn encode_into_writer(&self, mut buf: W) -> Result<(), EncodingError> { + let arr_len = 2 + match self.args { + Args::None => 0, + Args::One(_) => 1, + Args::Three(_, _, _) => 3, + }; + rmp::encode::write_array_len(&mut buf, arr_len)?; + rmp::encode::write_str(&mut buf, self.operation)?; + rmpv::encode::write_value_ref(&mut buf, &self.field_name)?; + match &self.args { + Args::None => {} + Args::One(x) => { + rmpv::encode::write_value_ref(&mut buf, x)?; + } + Args::Three(x, y, z) => { + rmpv::encode::write_value_ref(&mut buf, x)?; + rmpv::encode::write_value_ref(&mut buf, y)?; + rmpv::encode::write_value_ref(&mut buf, z)?; + } + } + Ok(()) + } +} + +/// Implementation for allow single operation to be used as argument for `update` and `upsert`. +impl<'a> Tuple for DmoOperation<'a> { + fn encode_into_writer(&self, mut buf: W) -> Result<(), EncodingError> { + rmp::encode::write_array_len(&mut buf, 1)?; + TupleElement::encode_into_writer(self, &mut buf)?; + Ok(()) + } +} + +#[derive(Debug)] +enum Args<'a> { + None, + One(rmpv::ValueRef<'a>), + Three(rmpv::ValueRef<'a>, rmpv::ValueRef<'a>, rmpv::ValueRef<'a>), +} + +mod ops { + pub(super) const ADD: &str = "+"; + pub(super) const SUB: &str = "-"; + pub(super) const AND: &str = "&"; + pub(super) const OR: &str = "|"; + pub(super) const XOR: &str = "^"; + pub(super) const STRING_SPLICE: &str = ":"; + pub(super) const INSERT: &str = "|"; + pub(super) const DEL: &str = "#"; + pub(super) const ASSIGN: &str = "="; +} diff --git a/src/client/dmo_response.rs b/src/client/dmo/response.rs similarity index 100% rename from src/client/dmo_response.rs rename to src/client/dmo/response.rs diff --git a/src/client/executor_ext.rs b/src/client/executor_ext.rs index 86cee6c..36b606a 100644 --- a/src/client/executor_ext.rs +++ b/src/client/executor_ext.rs @@ -3,7 +3,6 @@ use futures::{future::BoxFuture, FutureExt}; use rmpv::Value; use serde::de::DeserializeOwned; -use super::{prepared_sql_statement::PreparedSqlStatement, Executor}; use crate::{ codec::request::{ Call, Delete, EncodedRequest, Eval, Execute, Insert, Ping, Prepare, Replace, Request, @@ -12,7 +11,7 @@ use crate::{ schema::{SchemaEntityKey, Space}, tuple::Tuple, utils::extract_and_deserialize_iproto_data, - CallResponse, DmoResponse, IteratorType, Result, SqlResponse, + CallResponse, DmoResponse, Executor, IteratorType, PreparedSqlStatement, Result, SqlResponse, }; /// Helper trait around [`Executor`] trait, which allows to send specific requests @@ -93,6 +92,7 @@ pub trait ExecutorExt: Executor { )) } + // TODO: docs and doctests for DmoOperation /// Update tuple. async fn update( &self, @@ -122,7 +122,6 @@ pub trait ExecutorExt: Executor { )) } - // TODO: structured tuple /// Insert a tuple into a space. If a tuple with the same primary key already exists, /// replaces the existing tuple with a new one. async fn replace(&self, space_id: u32, tuple: T) -> Result @@ -134,7 +133,6 @@ pub trait ExecutorExt: Executor { )) } - // TODO: structured tuple /// Delete a tuple identified by the primary key. async fn delete(&self, space_id: u32, index_id: u32, keys: T) -> Result where diff --git a/src/client/mod.rs b/src/client/mod.rs index 6638b9f..beabe2b 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,24 +1,23 @@ pub use self::{ call_response::CallResponse, connection::Connection, - dmo_response::DmoResponse, + dmo::{DmoOperation, DmoResponse}, executor::Executor, executor_ext::ExecutorExt, - prepared_sql_statement::PreparedSqlStatement, - sql_response::SqlResponse, + sql::{PreparedSqlStatement, SqlResponse}, stream::Stream, transaction::{Transaction, TransactionBuilder}, }; +// TODO: either reimport everything from schema or add dmo and sql mods pub mod schema; mod call_response; mod connection; -mod dmo_response; +mod dmo; mod executor; mod executor_ext; -mod prepared_sql_statement; -mod sql_response; +mod sql; mod stream; mod transaction; diff --git a/src/client/sql/mod.rs b/src/client/sql/mod.rs new file mode 100644 index 0000000..8452b77 --- /dev/null +++ b/src/client/sql/mod.rs @@ -0,0 +1,4 @@ +pub use self::{prepared_statement::PreparedSqlStatement, response::SqlResponse}; + +mod prepared_statement; +mod response; diff --git a/src/client/prepared_sql_statement.rs b/src/client/sql/prepared_statement.rs similarity index 100% rename from src/client/prepared_sql_statement.rs rename to src/client/sql/prepared_statement.rs diff --git a/src/client/sql_response.rs b/src/client/sql/response.rs similarity index 100% rename from src/client/sql_response.rs rename to src/client/sql/response.rs diff --git a/src/lib.rs b/src/lib.rs index 02129a0..050c921 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,7 +82,7 @@ pub use self::{ client::*, codec::consts::{IteratorType, TransactionIsolationLevel}, errors::Error, - tuple::Tuple, + tuple::{Tuple, TupleElement}, }; pub mod errors; diff --git a/src/tuple.rs b/src/tuple.rs index 77097bb..da77e6c 100644 --- a/src/tuple.rs +++ b/src/tuple.rs @@ -1,9 +1,18 @@ use std::io::Write; -use serde::Serialize; - use crate::errors::EncodingError; +pub trait TupleElement { + fn encode_into_writer(&self, buf: W) -> Result<(), EncodingError>; +} + +impl TupleElement for T { + fn encode_into_writer(&self, mut buf: W) -> Result<(), EncodingError> { + rmp_serde::encode::write(&mut buf, self)?; + Ok(()) + } +} + /// Trait, describing type, which can be encoded into /// MessagePack tuple. /// @@ -13,11 +22,21 @@ pub trait Tuple { fn encode_into_writer(&self, buf: W) -> Result<(), EncodingError>; } -impl Tuple for Vec { +impl Tuple for Vec { + fn encode_into_writer(&self, mut buf: W) -> Result<(), EncodingError> { + rmp::encode::write_array_len(&mut buf, self.len() as u32)?; + for x in self.iter() { + x.encode_into_writer(&mut buf)?; + } + Ok(()) + } +} + +impl Tuple for &[T] { fn encode_into_writer(&self, mut buf: W) -> Result<(), EncodingError> { rmp::encode::write_array_len(&mut buf, self.len() as u32)?; for x in self.iter() { - rmp_serde::encode::write(&mut buf, &x)?; + x.encode_into_writer(&mut buf)?; } Ok(()) } @@ -39,26 +58,26 @@ impl Tuple for &T { // `= self` idea is from https://stackoverflow.com/a/56700760/5033855 macro_rules! impl_tuple_for_tuple { ( $param:tt ) => { - impl<$param : serde::Serialize> Tuple for ($param,) { + impl<$param : $crate::TupleElement> Tuple for ($param,) { fn encode_into_writer(&self, mut buf: W) -> Result<(), EncodingError> { rmp::encode::write_array_len(&mut buf, 1)?; - rmp_serde::encode::write(&mut buf, &self.0)?; + self.0.encode_into_writer(&mut buf)?; Ok(()) } } }; ( $param:tt, $($params:tt),* ) => { - impl<$param : serde::Serialize, $($params : serde::Serialize,)*> Tuple for ($param, $($params,)*) { + impl<$param : $crate::TupleElement , $($params : $crate::TupleElement,)*> Tuple for ($param, $($params,)*) { #[allow(non_snake_case)] fn encode_into_writer(&self, mut buf: W) -> Result<(), EncodingError> { rmp::encode::write_array_len(&mut buf, count_tts!($param $($params)+) as u32)?; let ($param, $($params,)+) = self; - rmp_serde::encode::write(&mut buf, $param)?; + $param.encode_into_writer(&mut buf)?; $( - rmp_serde::encode::write(&mut buf, $params)?; + $params.encode_into_writer(&mut buf)?; )+ Ok(())