1use regex::bytes::Regex;
4use serde::{Deserialize, Serialize};
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::Arc;
7
8use crate::error::{Error, Result};
9use crate::types::{Key, Value};
10
11pub const MAX_KEY_PATTERN_BYTES: usize = 4 * 1024;
13pub const MAX_KEY_SEARCH_LIMIT: usize = 10_000;
15pub const MAX_KEY_SEARCH_SCAN_BUDGET: usize = 1_000_000;
17pub const DEFAULT_KEY_SEARCH_RESPONSE_BYTES: usize = 16 * 1024 * 1024;
19pub const MAX_KEY_SEARCH_RESPONSE_BYTES: usize = 100 * 1024 * 1024;
21
22#[derive(Debug, Clone, Default)]
24pub struct KeySearchCancellation(Arc<AtomicBool>);
25
26impl KeySearchCancellation {
27 pub fn cancel(&self) {
29 self.0.store(true, Ordering::Release);
30 }
31
32 pub fn is_cancelled(&self) -> bool {
34 self.0.load(Ordering::Acquire)
35 }
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40#[serde(tag = "mode", rename_all = "snake_case")]
41pub enum KeyPattern {
42 Glob {
45 pattern: Vec<u8>,
47 },
48 Regex {
50 pattern: String,
52 },
53}
54
55impl KeyPattern {
56 pub fn glob(pattern: impl AsRef<[u8]>) -> Self {
58 Self::Glob {
59 pattern: pattern.as_ref().to_vec(),
60 }
61 }
62
63 pub fn regex(pattern: impl Into<String>) -> Self {
65 Self::Regex {
66 pattern: pattern.into(),
67 }
68 }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73pub struct KeySearchRequest {
74 pub pattern: KeyPattern,
76 pub cursor: Option<Key>,
78 pub limit: usize,
80 pub scan_budget: usize,
82 #[serde(default = "default_response_bytes")]
84 pub max_bytes: usize,
85}
86
87impl KeySearchRequest {
88 pub fn new(pattern: KeyPattern, limit: usize, scan_budget: usize) -> Self {
90 Self {
91 pattern,
92 cursor: None,
93 limit,
94 scan_budget,
95 max_bytes: DEFAULT_KEY_SEARCH_RESPONSE_BYTES,
96 }
97 }
98
99 pub fn after(mut self, cursor: Key) -> Self {
101 self.cursor = Some(cursor);
102 self
103 }
104
105 pub fn with_max_bytes(mut self, max_bytes: usize) -> Self {
107 self.max_bytes = max_bytes;
108 self
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
114pub struct KeySearchEntry {
115 pub key: Key,
117 pub value: Value,
119}
120
121#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
123pub struct KeySearchPage {
124 pub entries: Vec<KeySearchEntry>,
126 pub next_cursor: Option<Key>,
128 pub scanned: usize,
130}
131
132pub(crate) struct PreparedKeySearch {
133 matcher: Regex,
134 prefix: Key,
135}
136
137impl PreparedKeySearch {
138 pub(crate) fn new(request: &KeySearchRequest) -> Result<Self> {
139 validate_request(request)?;
140 let (source, prefix) = match &request.pattern {
141 KeyPattern::Glob { pattern } => (glob_regex(pattern)?, glob_prefix(pattern)?),
142 KeyPattern::Regex { pattern } => (pattern.clone(), Vec::new()),
146 };
147 let matcher = Regex::new(&source).map_err(|error| Error::InvalidParameter {
148 param: "pattern".into(),
149 reason: error.to_string(),
150 })?;
151 Ok(Self { matcher, prefix })
152 }
153
154 pub(crate) fn prefix(&self) -> &[u8] {
155 &self.prefix
156 }
157
158 pub(crate) fn collect(
159 &self,
160 mut next: impl FnMut() -> Result<Option<(Key, Option<Value>)>>,
161 request: &KeySearchRequest,
162 cancellation: &KeySearchCancellation,
163 ) -> Result<KeySearchPage> {
164 let mut entries = Vec::with_capacity(request.limit);
165 let mut scanned = 0usize;
166 let mut scanned_bytes = 0usize;
167 let mut response_bytes = 0usize;
168 loop {
169 if cancellation.is_cancelled() {
170 return Err(Error::SearchCancelled);
171 }
172 if scanned == request.scan_budget {
173 return Err(Error::SearchBudgetExceeded {
174 limit: request.scan_budget,
175 });
176 }
177 let Some((key, value)) = next()? else {
178 break;
179 };
180 if !self.prefix.is_empty() && !key.starts_with(&self.prefix) {
181 break;
182 }
183 scanned += 1;
184 scanned_bytes = scanned_bytes
185 .saturating_add(key.len())
186 .saturating_add(value.as_ref().map_or(0, Vec::len));
187 if scanned_bytes > request.max_bytes {
188 return Err(Error::SearchResponseTooLarge {
189 limit: request.max_bytes,
190 requested: scanned_bytes,
191 });
192 }
193 if let Some(value) = value.filter(|_| self.matcher.is_match(&key)) {
194 let cursor_bytes = if entries.len() + 1 == request.limit {
195 key.len()
196 } else {
197 0
198 };
199 let requested = response_bytes
200 .saturating_add(key.len())
201 .saturating_add(value.len())
202 .saturating_add(cursor_bytes);
203 if requested > request.max_bytes {
204 return Err(Error::SearchResponseTooLarge {
205 limit: request.max_bytes,
206 requested,
207 });
208 }
209 response_bytes = requested;
210 entries.push(KeySearchEntry { key, value });
211 if entries.len() == request.limit {
212 break;
213 }
214 }
215 }
216 let next_cursor = (entries.len() == request.limit)
217 .then(|| entries.last().expect("non-empty full page").key.clone());
218 Ok(KeySearchPage {
219 entries,
220 next_cursor,
221 scanned,
222 })
223 }
224}
225
226fn validate_request(request: &KeySearchRequest) -> Result<()> {
227 let pattern_len = match &request.pattern {
228 KeyPattern::Glob { pattern } => pattern.len(),
229 KeyPattern::Regex { pattern } => pattern.len(),
230 };
231 if pattern_len > MAX_KEY_PATTERN_BYTES {
232 return invalid(
233 "pattern",
234 format!("must be at most {MAX_KEY_PATTERN_BYTES} bytes"),
235 );
236 }
237 if !(1..=MAX_KEY_SEARCH_LIMIT).contains(&request.limit) {
238 return invalid(
239 "limit",
240 format!("must be between 1 and {MAX_KEY_SEARCH_LIMIT}"),
241 );
242 }
243 if !(1..=MAX_KEY_SEARCH_SCAN_BUDGET).contains(&request.scan_budget) {
244 return invalid(
245 "scan_budget",
246 format!("must be between 1 and {MAX_KEY_SEARCH_SCAN_BUDGET}"),
247 );
248 }
249 if !(1..=MAX_KEY_SEARCH_RESPONSE_BYTES).contains(&request.max_bytes) {
250 return invalid(
251 "max_bytes",
252 format!("must be between 1 and {MAX_KEY_SEARCH_RESPONSE_BYTES}"),
253 );
254 }
255 Ok(())
256}
257
258fn default_response_bytes() -> usize {
259 DEFAULT_KEY_SEARCH_RESPONSE_BYTES
260}
261
262fn invalid<T>(param: &str, reason: String) -> Result<T> {
263 Err(Error::InvalidParameter {
264 param: param.into(),
265 reason,
266 })
267}
268
269fn glob_regex(pattern: &[u8]) -> Result<String> {
270 let mut source = String::from("^(?-u:");
271 let mut escaped = false;
272 for &byte in pattern {
273 if escaped {
274 push_byte(&mut source, byte);
275 escaped = false;
276 } else {
277 match byte {
278 b'\\' => escaped = true,
279 b'*' => source.push_str("(?s:.*)"),
280 b'?' => source.push_str("(?s:.)"),
281 literal => push_byte(&mut source, literal),
282 }
283 }
284 }
285 if escaped {
286 return invalid("pattern", "glob ends with an escape byte".into());
287 }
288 source.push_str(")$");
289 Ok(source)
290}
291
292fn glob_prefix(pattern: &[u8]) -> Result<Key> {
293 let mut prefix = Vec::new();
294 let mut escaped = false;
295 for &byte in pattern {
296 if escaped {
297 prefix.push(byte);
298 escaped = false;
299 } else {
300 match byte {
301 b'\\' => escaped = true,
302 b'*' | b'?' => break,
303 literal => prefix.push(literal),
304 }
305 }
306 }
307 if escaped {
308 return invalid("pattern", "glob ends with an escape byte".into());
309 }
310 Ok(prefix)
311}
312
313fn push_byte(output: &mut String, byte: u8) {
314 use std::fmt::Write;
315 write!(output, "\\x{byte:02X}").expect("writing to String cannot fail");
316}