surrealdb_core/sql/
regex.rs

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