surrealdb_core/sql/
regex.rs

1use once_cell::sync::Lazy;
2use quick_cache::sync::{Cache, GuardResult};
3use revision::revisioned;
4use serde::{
5	de::{self, Visitor},
6	Deserialize, Deserializer, Serialize, Serializer,
7};
8use std::cmp::Ordering;
9use std::fmt::Debug;
10use std::fmt::{self, Display, Formatter};
11use std::hash::{Hash, Hasher};
12use std::str::FromStr;
13use std::{env, str};
14
15pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Regex";
16
17#[revisioned(revision = 1)]
18#[derive(Clone)]
19#[non_exhaustive]
20pub struct Regex(pub regex::Regex);
21
22impl Regex {
23	// Deref would expose `regex::Regex::as_str` which wouldn't have the '/' delimiters.
24	pub fn regex(&self) -> &regex::Regex {
25		&self.0
26	}
27}
28
29fn regex_new(str: &str) -> Result<regex::Regex, regex::Error> {
30	static REGEX_CACHE: Lazy<Cache<String, regex::Regex>> = Lazy::new(|| {
31		let cache_size: usize = env::var("SURREAL_REGEX_CACHE_SIZE")
32			.map_or(1000, |v| v.parse().unwrap_or(1000))
33			.max(10); // The minimum cache size is 10
34		Cache::new(cache_size)
35	});
36	match REGEX_CACHE.get_value_or_guard(str, None) {
37		GuardResult::Value(v) => Ok(v),
38		GuardResult::Guard(g) => {
39			let re = regex::Regex::new(str)?;
40			g.insert(re.clone()).ok();
41			Ok(re)
42		}
43		GuardResult::Timeout => {
44			warn!("Regex cache timeout");
45			regex::Regex::new(str)
46		}
47	}
48}
49
50impl FromStr for Regex {
51	type Err = <regex::Regex as FromStr>::Err;
52
53	fn from_str(s: &str) -> Result<Self, Self::Err> {
54		if s.contains('\0') {
55			Err(regex::Error::Syntax("regex contained NUL byte".to_owned()))
56		} else {
57			regex_new(&s.replace("\\/", "/")).map(Self)
58		}
59	}
60}
61
62impl PartialEq for Regex {
63	fn eq(&self, other: &Self) -> bool {
64		let str_left = self.0.as_str();
65		let str_right = other.0.as_str();
66		str_left == str_right
67	}
68}
69
70impl Eq for Regex {}
71
72impl Ord for Regex {
73	fn cmp(&self, other: &Self) -> Ordering {
74		self.0.as_str().cmp(other.0.as_str())
75	}
76}
77
78impl PartialOrd for Regex {
79	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
80		Some(self.cmp(other))
81	}
82}
83
84impl Hash for Regex {
85	fn hash<H: Hasher>(&self, state: &mut H) {
86		self.0.as_str().hash(state);
87	}
88}
89
90impl Debug for Regex {
91	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
92		Display::fmt(self, f)
93	}
94}
95
96impl Display for Regex {
97	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
98		write!(f, "/{}/", &self.0)
99	}
100}
101
102impl Serialize for Regex {
103	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
104	where
105		S: Serializer,
106	{
107		serializer.serialize_newtype_struct(TOKEN, self.0.as_str())
108	}
109}
110
111impl<'de> Deserialize<'de> for Regex {
112	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
113	where
114		D: Deserializer<'de>,
115	{
116		struct RegexNewtypeVisitor;
117
118		impl<'de> Visitor<'de> for RegexNewtypeVisitor {
119			type Value = Regex;
120
121			fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
122				formatter.write_str("a regex newtype")
123			}
124
125			fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
126			where
127				D: Deserializer<'de>,
128			{
129				struct RegexVisitor;
130
131				impl<'de> Visitor<'de> for RegexVisitor {
132					type Value = Regex;
133
134					fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
135						formatter.write_str("a regex str")
136					}
137
138					fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
139					where
140						E: de::Error,
141					{
142						Regex::from_str(value).map_err(|_| de::Error::custom("invalid regex"))
143					}
144				}
145
146				deserializer.deserialize_str(RegexVisitor)
147			}
148		}
149
150		deserializer.deserialize_newtype_struct(TOKEN, RegexNewtypeVisitor)
151	}
152}