1use crate::ValkeyResult;
7use crate::cmd::Cmd;
8use crate::error::GlideError;
9use crate::pipeline_options::PipelineOptions;
10use crate::value::ValkeyValue;
11use crate::write::ToValkeyArgs;
12use crate::write::ValkeyWrite;
13use glide_core::client::Client as CoreClient;
14use redis::PipelineRetryStrategy;
15use redis::cluster_routing::RoutingInfo;
16
17pub fn pipe() -> Pipeline {
21 Pipeline::new()
22}
23
24#[derive(Clone, Debug, Default)]
40pub struct Pipeline {
41 inner: redis::Pipeline,
42}
43
44impl Pipeline {
45 pub fn new() -> Self {
47 Pipeline {
48 inner: redis::Pipeline::new(),
49 }
50 }
51
52 pub fn atomic(&mut self) -> &mut Self {
55 self.inner.atomic();
56 self
57 }
58
59 pub fn cmd(&mut self, name: &str) -> &mut Self {
61 self.inner.cmd(name);
62 self
63 }
64
65 pub fn arg<A: ToValkeyArgs>(&mut self, arg: A) -> &mut Self {
67 arg.write_valkey_args(self);
68 self
69 }
70
71 pub fn ignore(&mut self) -> &mut Self {
74 self.inner.ignore();
75 self
76 }
77
78 pub(crate) fn add_command(&mut self, cmd: Cmd) -> &mut Self {
80 self.inner.add_command(cmd.into_redis());
81 self
82 }
83
84 pub(crate) fn as_redis(&self) -> &redis::Pipeline {
87 &self.inner
88 }
89}
90
91impl ValkeyWrite for Pipeline {
93 fn write_arg(&mut self, arg: &[u8]) {
94 self.inner.arg(arg);
95 }
96
97 fn write_arg_fmt(&mut self, arg: impl std::fmt::Display) {
98 self.inner.arg(arg.to_string().into_bytes());
99 }
100}
101
102pub(crate) async fn dispatch_pipeline(
104 core: &CoreClient,
105 pipeline: &Pipeline,
106 routing: Option<RoutingInfo>,
107 raise_on_error: bool,
108 options: &PipelineOptions,
109) -> ValkeyResult<ValkeyValue> {
110 let redis_pipeline = pipeline.as_redis();
111
112 if redis_pipeline.is_empty() {
114 return Ok(ValkeyValue::Array(Vec::new()));
115 }
116
117 let timeout = options.timeout_millis();
118 let mut client = core.clone();
119
120 let reply = if redis_pipeline.is_atomic() {
121 client
122 .send_transaction(redis_pipeline, routing, timeout, raise_on_error)
123 .await
124 } else {
125 let retry_strategy = PipelineRetryStrategy {
126 retry_server_error: options.retry_server_error,
127 retry_connection_error: options.retry_connection_error,
128 };
129 client
130 .send_pipeline(
131 redis_pipeline,
132 routing,
133 raise_on_error,
134 timeout,
135 retry_strategy,
136 )
137 .await
138 };
139
140 ValkeyValue::from_redis(reply.map_err(GlideError::from_redis_error)?)
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn get_packed_pipeline_matches_redis() {
149 for atomic in [false, true] {
150 let mut glide_pipe = pipe();
151 let mut redis_pipe = redis::pipe();
152
153 if atomic {
154 glide_pipe.atomic();
155 redis_pipe.atomic();
156 }
157
158 glide_pipe
160 .set("k", "v")
161 .incr("k", 1)
162 .ignore()
163 .get("k")
164 .rpush("l", &["a", "b"][..]);
165 redis_pipe
166 .set("k", "v")
167 .incr("k", 1)
168 .ignore()
169 .get("k")
170 .rpush("l", &["a", "b"][..]);
171
172 let big = vec![0xABu8; 4096];
174 glide_pipe
175 .cmd("APPEND")
176 .arg("k")
177 .arg(&big)
178 .arg(&["a", "b", "c"][..]);
179 redis_pipe
180 .cmd("APPEND")
181 .arg("k")
182 .arg(&big)
183 .arg(&["a", "b", "c"][..]);
184
185 assert_eq!(
186 glide_pipe.as_redis().get_packed_pipeline(),
187 redis_pipe.get_packed_pipeline(),
188 "glide pipeline encoding diverged from redis (atomic = {atomic})",
189 );
190 }
191 }
192}